robe-server 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.rspec +3 -0
- data/.travis.yml +19 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +85 -0
- data/LICENSE +674 -0
- data/README.md +21 -0
- data/Rakefile +10 -0
- data/lib/robe-server.rb +39 -0
- data/lib/robe/core_ext.rb +37 -0
- data/lib/robe/jvisor.rb +14 -0
- data/lib/robe/sash.rb +215 -0
- data/lib/robe/sash/doc_for.rb +57 -0
- data/lib/robe/sash/includes_tracker.rb +75 -0
- data/lib/robe/scanners.rb +49 -0
- data/lib/robe/server.rb +87 -0
- data/lib/robe/type_space.rb +57 -0
- data/lib/robe/visor.rb +56 -0
- data/robe-server.gemspec +19 -0
- data/spec/robe/jvisor_spec.rb +30 -0
- data/spec/robe/method_scanner_spec.rb +50 -0
- data/spec/robe/module_scanner_spec.rb +76 -0
- data/spec/robe/sash/doc_for_spec.rb +165 -0
- data/spec/robe/sash/includes_tracker_spec.rb +35 -0
- data/spec/robe/sash_spec.rb +420 -0
- data/spec/robe/server_spec.rb +64 -0
- data/spec/robe/type_space_spec.rb +126 -0
- data/spec/robe/visor_spec.rb +91 -0
- data/spec/robe_spec.rb +51 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/support/mocks.rb +31 -0
- metadata +116 -0
| @@ -0,0 +1,49 @@ | |
| 1 | 
            +
            require 'robe/core_ext'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Robe
         | 
| 4 | 
            +
              class Scanner
         | 
| 5 | 
            +
                attr_accessor :check_private
         | 
| 6 | 
            +
                attr_reader :candidates
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def initialize(sym, check_private)
         | 
| 9 | 
            +
                  @candidates = []
         | 
| 10 | 
            +
                  @sym = sym
         | 
| 11 | 
            +
                  @check_private = check_private
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def scan(modules, check_instance, check_module)
         | 
| 15 | 
            +
                  modules.each do |m|
         | 
| 16 | 
            +
                    if check_module
         | 
| 17 | 
            +
                      sc = m.__singleton_class__
         | 
| 18 | 
            +
                      scan_methods(sc, :__instance_methods__)
         | 
| 19 | 
            +
                      scan_methods(sc, :__private_instance_methods__) if check_private
         | 
| 20 | 
            +
                    end
         | 
| 21 | 
            +
                    if check_instance
         | 
| 22 | 
            +
                      scan_methods(m, :__instance_methods__)
         | 
| 23 | 
            +
                      scan_methods(m, :__private_instance_methods__) if check_private
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              class ModuleScanner < Scanner
         | 
| 30 | 
            +
                def scan_methods(mod, coll)
         | 
| 31 | 
            +
                  if mod.__send__(coll, false).include?(@sym)
         | 
| 32 | 
            +
                    candidates << mod.instance_method(@sym)
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              class MethodScanner < Scanner
         | 
| 38 | 
            +
                def initialize(*args)
         | 
| 39 | 
            +
                  super
         | 
| 40 | 
            +
                  @re = /^#{Regexp.escape(@sym || "")}/
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def scan_methods(mod, coll)
         | 
| 44 | 
            +
                  mod.__send__(coll, false).grep(@re) do |sym|
         | 
| 45 | 
            +
                    candidates << mod.instance_method(sym)
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
            end
         | 
    
        data/lib/robe/server.rb
    ADDED
    
    | @@ -0,0 +1,87 @@ | |
| 1 | 
            +
            require 'json'
         | 
| 2 | 
            +
            require 'tmpdir'
         | 
| 3 | 
            +
            require 'socket'
         | 
| 4 | 
            +
            require 'webrick'
         | 
| 5 | 
            +
            require 'logger'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module Robe
         | 
| 8 | 
            +
              class Server
         | 
| 9 | 
            +
                attr_reader :running, :port
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def initialize(handler, port)
         | 
| 12 | 
            +
                  @handler = handler
         | 
| 13 | 
            +
                  @server = TCPServer.new("127.0.0.1", port)
         | 
| 14 | 
            +
                  @running = true
         | 
| 15 | 
            +
                  @port = @server.addr[1]
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def start
         | 
| 19 | 
            +
                  access = File.open("#{Dir.tmpdir}/robe-access-#{@port}.log", "w")
         | 
| 20 | 
            +
                  access.sync = true
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  error_logger = Logger.new($stderr)
         | 
| 23 | 
            +
                  access_logger = Logger.new(access)
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  client = nil
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  loop do
         | 
| 28 | 
            +
                    begin
         | 
| 29 | 
            +
                      client = @server.accept
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                      next if client.eof?
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                      req = WEBrick::HTTPRequest.new(:InputBufferSize => 1024,
         | 
| 34 | 
            +
                                                     :Logger => error_logger)
         | 
| 35 | 
            +
                      req.parse(client)
         | 
| 36 | 
            +
                      access_logger.info "#{req.request_method} #{req.path}"
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                      begin
         | 
| 39 | 
            +
                        body = @handler.call(req.path, req.body)
         | 
| 40 | 
            +
                      rescue Exception => e
         | 
| 41 | 
            +
                        error_logger.error "Request failed: #{req.path}. Please file an issue."
         | 
| 42 | 
            +
                        error_logger.error "#{e.message}\n#{e.backtrace.join("\n")}"
         | 
| 43 | 
            +
                      end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                      resp = WEBrick::HTTPResponse.new(:OutputBufferSize => 1024,
         | 
| 46 | 
            +
                                                       :Logger => error_logger,
         | 
| 47 | 
            +
                                                       :HTTPVersion => "1.1")
         | 
| 48 | 
            +
                      resp.status = 200
         | 
| 49 | 
            +
                      resp.content_type = "application/json; charset=utf-8"
         | 
| 50 | 
            +
                      resp.body = body
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                      begin
         | 
| 53 | 
            +
                        resp.send_response(client)
         | 
| 54 | 
            +
                        client.close
         | 
| 55 | 
            +
                      rescue Errno::EPIPE
         | 
| 56 | 
            +
                        error_logger.error "Connection lost, unsent response:"
         | 
| 57 | 
            +
                        error_logger.error body
         | 
| 58 | 
            +
                      end
         | 
| 59 | 
            +
                    rescue Errno::EINVAL
         | 
| 60 | 
            +
                      break
         | 
| 61 | 
            +
                    rescue IOError
         | 
| 62 | 
            +
                      # Hello JRuby
         | 
| 63 | 
            +
                      break
         | 
| 64 | 
            +
                    end
         | 
| 65 | 
            +
                  end
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                def wait_for_it
         | 
| 69 | 
            +
                  begin
         | 
| 70 | 
            +
                    TCPSocket.new("127.0.0.1", @port).close
         | 
| 71 | 
            +
                  rescue
         | 
| 72 | 
            +
                    sleep 0.05
         | 
| 73 | 
            +
                    retry
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                def shutdown
         | 
| 78 | 
            +
                  @running = false
         | 
| 79 | 
            +
                  begin
         | 
| 80 | 
            +
                    @server && @server.shutdown(Socket::SHUT_RDWR)
         | 
| 81 | 
            +
                  rescue Errno::ENOTCONN
         | 
| 82 | 
            +
                    # Hello JRuby
         | 
| 83 | 
            +
                    @server.close
         | 
| 84 | 
            +
                  end
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
              end
         | 
| 87 | 
            +
            end
         | 
| @@ -0,0 +1,57 @@ | |
| 1 | 
            +
            require 'robe/sash'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Robe
         | 
| 4 | 
            +
              class TypeSpace
         | 
| 5 | 
            +
                attr_reader :visor, :target_type, :instance
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def initialize(visor, target, mod, instance, superc)
         | 
| 8 | 
            +
                  @visor = visor
         | 
| 9 | 
            +
                  @instance = instance
         | 
| 10 | 
            +
                  @superc = superc
         | 
| 11 | 
            +
                  guess_target_type(target, mod)
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def scan_with(scanner)
         | 
| 15 | 
            +
                  return unless obj = target_type
         | 
| 16 | 
            +
                  modules = obj.ancestors
         | 
| 17 | 
            +
                  modules -= obj.included_modules unless instance
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  if @superc
         | 
| 20 | 
            +
                    modules -= [obj]
         | 
| 21 | 
            +
                  else
         | 
| 22 | 
            +
                    modules += visor.descendants(obj).to_a
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  modules.push(Kernel) if instance && !obj.is_a?(Class)
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  if instance
         | 
| 28 | 
            +
                    if defined? ActiveSupport::Concern and obj.is_a?(ActiveSupport::Concern)
         | 
| 29 | 
            +
                      deps = obj.instance_variable_get("@_dependencies")
         | 
| 30 | 
            +
                      modules += deps if deps
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  scanner.scan(modules, instance, !instance)
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  unless instance
         | 
| 37 | 
            +
                    singleton_ancestors = obj.singleton_class.ancestors
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    if RUBY_VERSION >= "2.1.0"
         | 
| 40 | 
            +
                      # Ruby 2.1 includes all singletons in the ancestors chain
         | 
| 41 | 
            +
                      singleton_ancestors.reject!(&:__singleton_class__?)
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                    scanner.scan(singleton_ancestors, true, false)
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                private
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                def guess_target_type(target, mod)
         | 
| 51 | 
            +
                  @target_type = visor.resolve_context(target, mod)
         | 
| 52 | 
            +
                  if @target_type && !(@target_type.is_a? Module)
         | 
| 53 | 
            +
                    @target_type, @instance = @target_type.class, true
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
            end
         | 
    
        data/lib/robe/visor.rb
    ADDED
    
    | @@ -0,0 +1,56 @@ | |
| 1 | 
            +
            require 'forwardable'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Robe
         | 
| 4 | 
            +
              class Visor
         | 
| 5 | 
            +
                extend Forwardable
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def each_object(*args)
         | 
| 8 | 
            +
                  ObjectSpace.each_object(*args).reject { |m| m.__singleton_class__? }
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def descendants(cls)
         | 
| 12 | 
            +
                  each_object(cls.singleton_class).to_a - [cls]
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def resolve_context(name, mod)
         | 
| 16 | 
            +
                  return resolve_const(mod) unless name
         | 
| 17 | 
            +
                  unless name =~ /\A::/
         | 
| 18 | 
            +
                    nesting = mod ? mod.split("::") : []
         | 
| 19 | 
            +
                    resolve_path_elems(nesting).reverse.each do |elem|
         | 
| 20 | 
            +
                      begin
         | 
| 21 | 
            +
                        return elem.const_get(name)
         | 
| 22 | 
            +
                      rescue NameError
         | 
| 23 | 
            +
                      end
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
                  resolve_const(name)
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def resolve_const(name)
         | 
| 30 | 
            +
                  resolve_path(name).last
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def resolve_path(name)
         | 
| 34 | 
            +
                  return [] unless name
         | 
| 35 | 
            +
                  return [ARGF.class] if name == "ARGF.class"
         | 
| 36 | 
            +
                  if %w(IO::readable IO::writable).include?(name)
         | 
| 37 | 
            +
                    return [StringIO.included_modules.find { |m| m.name == name }]
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                  nesting = name.split("::")
         | 
| 40 | 
            +
                  nesting.shift if nesting[0].empty?
         | 
| 41 | 
            +
                  resolve_path_elems(nesting)
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def resolve_path_elems(nesting, init = Object)
         | 
| 45 | 
            +
                  c = init; ary = []
         | 
| 46 | 
            +
                  begin
         | 
| 47 | 
            +
                    nesting.each do |name|
         | 
| 48 | 
            +
                      ary << (c = c.const_get(name))
         | 
| 49 | 
            +
                    end
         | 
| 50 | 
            +
                    ary
         | 
| 51 | 
            +
                  rescue NameError
         | 
| 52 | 
            +
                    []
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
            end
         | 
    
        data/robe-server.gemspec
    ADDED
    
    | @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            Gem::Specification.new do |gem|
         | 
| 2 | 
            +
              gem.name = 'robe-server'
         | 
| 3 | 
            +
              gem.version = '1.0.2'
         | 
| 4 | 
            +
              gem.authors = 'Phil Hagelberg, Dmitry Gutov, Alexsey Ermolaev'
         | 
| 5 | 
            +
              gem.email = 'afay.zangetsu@gmail.com'
         | 
| 6 | 
            +
              gem.homepage = 'https://github.com/AfsmNGhr/robe-server'
         | 
| 7 | 
            +
              gem.description = 'Server for robe'
         | 
| 8 | 
            +
              gem.summary = 'Code navigation, documentation lookup and completion for Ruby'
         | 
| 9 | 
            +
              gem.license = 'GPL-3.0'
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              gem.add_development_dependency 'rake', '~> 10.3'
         | 
| 12 | 
            +
              gem.add_development_dependency 'rspec', '~> 2.14'
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              gem.files = `git ls-files`.split("\n")
         | 
| 15 | 
            +
              gem.require_paths = ['lib']
         | 
| 16 | 
            +
              gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              gem.extra_rdoc_files = ['LICENSE', 'README.md']
         | 
| 19 | 
            +
            end
         | 
| @@ -0,0 +1,30 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
            require 'robe/jvisor'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            describe Robe::JVisor do
         | 
| 5 | 
            +
              subject { Robe::JVisor.new }
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              it "iterates though all native classes" do
         | 
| 8 | 
            +
                res = subject.each_object(Module)
         | 
| 9 | 
            +
                expect(res).to include(String)
         | 
| 10 | 
            +
                expect(res).to include(Set)
         | 
| 11 | 
            +
                expect(res).to include(Enumerable)
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              it "returns only modules responding to :name and :instance_methods" do
         | 
| 15 | 
            +
                res = subject.each_object(Module)
         | 
| 16 | 
            +
                expect(res.all? { |m| m.respond_to? :name }).to be_true
         | 
| 17 | 
            +
                expect(res.all? { |m| m.respond_to? :instance_methods }).to be_true
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              it "returns descendants of a module" do
         | 
| 21 | 
            +
                res = subject.descendants(Enumerable)
         | 
| 22 | 
            +
                expect(res).to include(Array)
         | 
| 23 | 
            +
                expect(res).not_to include(String)
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              it "returns descendants of a class" do
         | 
| 27 | 
            +
                res = subject.descendants(Integer)
         | 
| 28 | 
            +
                expect(res).to match_array([Bignum, Fixnum])
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
            end
         | 
| @@ -0,0 +1,50 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
            require 'robe/scanners'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            describe Robe::MethodScanner do
         | 
| 5 | 
            +
              include ScannerHelper
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              klass = described_class
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              let(:a) { new_module(:f, :b, :b, :t) }
         | 
| 10 | 
            +
              let(:b) { new_module(:bar, :foo, :baz, :tee) }
         | 
| 11 | 
            +
              let(:c) { new_module(:foo, :bar, :baz, :tee) }
         | 
| 12 | 
            +
              let(:d) { new_module(:foo, :bar, :tee, :baz) }
         | 
| 13 | 
            +
              let(:modules) { [a, b, c, d] }
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              it "completes instance methods" do
         | 
| 16 | 
            +
                scanner = klass.new(:f, false)
         | 
| 17 | 
            +
                scanner.scan(modules, true, false)
         | 
| 18 | 
            +
                expect(scanner.candidates).to have_names(:f, :foo, :foo)
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              it "completes module methods" do
         | 
| 22 | 
            +
                scanner = klass.new(:b, false)
         | 
| 23 | 
            +
                scanner.scan(modules, false, true)
         | 
| 24 | 
            +
                expect(scanner.candidates).to have_names(:b, :bar, :bar)
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              it "completes private instance methods" do
         | 
| 28 | 
            +
                scanner = klass.new(:baz, true)
         | 
| 29 | 
            +
                scanner.scan(modules, true, false)
         | 
| 30 | 
            +
                expect(scanner.candidates).to have_names(:baz, :baz)
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              it "completes private module methods" do
         | 
| 34 | 
            +
                scanner = klass.new(:tee, true)
         | 
| 35 | 
            +
                scanner.scan(modules, false, true)
         | 
| 36 | 
            +
                expect(scanner.candidates).to have_names(:tee, :tee)
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              it "completes nothing when shouldn't" do
         | 
| 40 | 
            +
                scanner = klass.new(:foo, true)
         | 
| 41 | 
            +
                scanner.scan(modules, false, false)
         | 
| 42 | 
            +
                expect(scanner.candidates).to be_empty
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              RSpec::Matchers.define :have_names do |*names|
         | 
| 46 | 
            +
                match do |candidates|
         | 
| 47 | 
            +
                  candidates.map(&:name) == names
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
            end
         | 
| @@ -0,0 +1,76 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
            require 'robe/scanners'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            describe Robe::ModuleScanner do
         | 
| 5 | 
            +
              include ScannerHelper
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              klass = described_class
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              let(:a) { new_module(:foo, :bar, :baz, :tee) }
         | 
| 10 | 
            +
              let(:b) { new_module(:bar, :foo, :baz, :tee) }
         | 
| 11 | 
            +
              let(:c) { new_module(:foo, :bar, :tee, :baz) }
         | 
| 12 | 
            +
              let(:d) { new_module(:bar, :foo, :tee, :baz) }
         | 
| 13 | 
            +
              let(:e) { Module.new }
         | 
| 14 | 
            +
              let(:modules) { [a, b, c, d, e ] }
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              it "finds instance methods" do
         | 
| 17 | 
            +
                scanner = klass.new(:foo, false)
         | 
| 18 | 
            +
                scanner.scan(modules, true, false)
         | 
| 19 | 
            +
                expect(scanner.candidates).to eq [a, c].map { |s| s.instance_method(:foo) }
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              it "finds module methods" do
         | 
| 23 | 
            +
                scanner = klass.new(:foo, false)
         | 
| 24 | 
            +
                scanner.scan(modules, false, true)
         | 
| 25 | 
            +
                expect(scanner.candidates).to eq [b, d].map { |s| s.method(:foo).unbind }
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              it "find private instance methods" do
         | 
| 29 | 
            +
                scanner = klass.new(:baz, true)
         | 
| 30 | 
            +
                scanner.scan(modules, true, false)
         | 
| 31 | 
            +
                expect(scanner.candidates).to eq [a, b].map { |s| s.instance_method(:baz) }
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              it "finds private module methods" do
         | 
| 35 | 
            +
                scanner = klass.new(:tee, true)
         | 
| 36 | 
            +
                scanner.scan(modules, false, true)
         | 
| 37 | 
            +
                expect(scanner.candidates).to eq [a, b].map { |s| s.method(:tee).unbind }
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              it "finds nothing when shouldn't" do
         | 
| 41 | 
            +
                scanner = klass.new(:foo, true)
         | 
| 42 | 
            +
                scanner.scan(modules, false, false)
         | 
| 43 | 
            +
                expect(scanner.candidates).to eq []
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              it "finds both types when should" do
         | 
| 47 | 
            +
                scanner = klass.new(:foo, false)
         | 
| 48 | 
            +
                scanner.scan(modules, true, true)
         | 
| 49 | 
            +
                expect(scanner.candidates).to eq([a.instance_method(:foo),
         | 
| 50 | 
            +
                    b.method(:foo).unbind, c.instance_method(:foo), d.method(:foo).unbind])
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
              it "doesn't include methods from Module in module methods" do
         | 
| 54 | 
            +
                scanner = klass.new(:attr, true)
         | 
| 55 | 
            +
                scanner.scan([Module.new], true, true)
         | 
| 56 | 
            +
                expect(scanner.candidates).to be_empty
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
              it "doesn't pay attention to overridden [private_]instance_methods" do
         | 
| 60 | 
            +
                tiff = new_module(:foo, :bar, :bar, :foo)
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                tiff = Module.new do
         | 
| 63 | 
            +
                  def self.instance_methods(_ign)
         | 
| 64 | 
            +
                    [:fool, :me, :once]
         | 
| 65 | 
            +
                  end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                  def self.private_instance_methods(_ign)
         | 
| 68 | 
            +
                    [:fool, :me, :twice]
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                scanner = klass.new(:fool, true)
         | 
| 73 | 
            +
                scanner.scan([tiff], true, true)
         | 
| 74 | 
            +
                expect(scanner.candidates).to be_empty
         | 
| 75 | 
            +
              end
         | 
| 76 | 
            +
            end
         | 
| @@ -0,0 +1,165 @@ | |
| 1 | 
            +
            require 'robe/sash/doc_for'
         | 
| 2 | 
            +
            require 'uri'
         | 
| 3 | 
            +
            require 'active_support/core_ext/kernel' # https://github.com/pry/pry-doc/issues/16
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            describe Robe::Sash::DocFor do
         | 
| 6 | 
            +
              klass = described_class
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              it "returns hash for a basic class" do
         | 
| 9 | 
            +
                c = Class.new do
         | 
| 10 | 
            +
                  # Some words.
         | 
| 11 | 
            +
                  def quux(a, *b, &c); end
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                k = klass.new(c.instance_method(:quux))
         | 
| 15 | 
            +
                expect(k.format).to eq({docstring: "Some words.\n",
         | 
| 16 | 
            +
                                        source: "def quux(a, *b, &c); end\n",
         | 
| 17 | 
            +
                                        aliases: [],
         | 
| 18 | 
            +
                                        visibility: :public})
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              it "shows docs for built-in classes" do
         | 
| 22 | 
            +
                hash = klass.new(Hash.instance_method(:update)).format
         | 
| 23 | 
            +
                expect(hash[:aliases]).to eq([:merge!])
         | 
| 24 | 
            +
                if RUBY_ENGINE == "ruby"
         | 
| 25 | 
            +
                  expect(hash[:docstring]).to include("Adds the contents")
         | 
| 26 | 
            +
                  expect(hash[:source]).to include("rb_hash_foreach")
         | 
| 27 | 
            +
                else
         | 
| 28 | 
            +
                  expect(hash[:docstring]).to eq("")
         | 
| 29 | 
            +
                  expect(hash[:source]).to start_with("def merge!(other)\n")
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              it "shows docs for stdlib classes" do
         | 
| 34 | 
            +
                hash = klass.new(URI.method(:parse)).format
         | 
| 35 | 
            +
                expect(hash[:docstring]).to include("one of the URI's subclasses")
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
              it "returns private visibility for Kernel#puts" do
         | 
| 39 | 
            +
                expect(klass.new(Kernel.instance_method(:puts)).format[:visibility])
         | 
| 40 | 
            +
                  .to eq(:private)
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
              it "returns public visibility for Kernel.puts" do
         | 
| 44 | 
            +
                # And doesn't do "Scanning and caching *.c files".
         | 
| 45 | 
            +
                expect(klass.new(Kernel.method(:puts)).format[:visibility]).to eq(:public)
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
              it "returns protected visibility" do
         | 
| 49 | 
            +
                method = Class.new.class_exec do
         | 
| 50 | 
            +
                  protected
         | 
| 51 | 
            +
                  def foo; end
         | 
| 52 | 
            +
                  instance_method(:foo)
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
                expect(klass.new(method).format[:visibility]).to be(:protected)
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
              it "mentions pry-doc when relevant" do
         | 
| 58 | 
            +
                val = Pry.config.has_pry_doc
         | 
| 59 | 
            +
                Pry.config.has_pry_doc = false
         | 
| 60 | 
            +
                struct = described_class.method_struct(String.instance_method(:gsub))
         | 
| 61 | 
            +
                Pry.config.has_pry_doc = val
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                if RUBY_ENGINE == "ruby"
         | 
| 64 | 
            +
                  expect(struct.source).to be_nil
         | 
| 65 | 
            +
                  expect(struct.docstring).to match(/pry-doc/)
         | 
| 66 | 
            +
                else
         | 
| 67 | 
            +
                  expect(struct.docstring).to eq("")
         | 
| 68 | 
            +
                  expect(struct.source).to start_with("def gsub(")
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
              end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
              context "native methods" do
         | 
| 73 | 
            +
                let(:c) { described_class }
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                context "String#gsub info fields" do
         | 
| 76 | 
            +
                  let(:struct) { c.method_struct(String.instance_method(:gsub)) }
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  if RUBY_ENGINE == "ruby"
         | 
| 79 | 
            +
                    it { expect(struct.docstring).to start_with("Returns")}
         | 
| 80 | 
            +
                    it { expect(struct.source).to start_with("static VALUE") }
         | 
| 81 | 
            +
                  else
         | 
| 82 | 
            +
                    it { expect(struct.docstring).to eq("") }
         | 
| 83 | 
            +
                    it { expect(struct.source).to start_with("def gsub(") }
         | 
| 84 | 
            +
                  end
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                context "Array#map has an alias" do
         | 
| 88 | 
            +
                  let(:struct) { c.method_struct(Array.instance_method(:map)) }
         | 
| 89 | 
            +
                  it { expect(struct.aliases).to eq([:collect])}
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                context "Set#include? has an alias" do
         | 
| 93 | 
            +
                  let(:struct) { c.method_struct(Set.instance_method(:include?)) }
         | 
| 94 | 
            +
                  it { expect(struct.aliases).to eq([:member?])}
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                context "Know the appropriate amount about Kernel#is_a?" do
         | 
| 98 | 
            +
                  let(:struct) { c.method_struct(Kernel.instance_method(:is_a?)) }
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                  it { expect(struct.visibility).to be_nil }
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                  if RUBY_ENGINE == "ruby"
         | 
| 103 | 
            +
                    it { expect(struct.docstring).to include("one of the superclasses") }
         | 
| 104 | 
            +
                    it { expect(struct.aliases).to eq([:kind_of?]) }
         | 
| 105 | 
            +
                    it { expect(struct.source).to start_with("VALUE\nrb_obj_is_kind_of") }
         | 
| 106 | 
            +
                  else
         | 
| 107 | 
            +
                    it { expect(struct.docstring).to include("class or superclass") }
         | 
| 108 | 
            +
                    it { expect(struct.aliases).to eq([:kind_of?]) }
         | 
| 109 | 
            +
                    it { expect(struct.source).to start_with("def kind_of?(") }
         | 
| 110 | 
            +
                  end
         | 
| 111 | 
            +
                end
         | 
| 112 | 
            +
              end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
              context "pure methods" do
         | 
| 115 | 
            +
                let(:c) { described_class }
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                context "method quux defined" do
         | 
| 118 | 
            +
                  # First line,
         | 
| 119 | 
            +
                  # second line.
         | 
| 120 | 
            +
                  def quux(n)
         | 
| 121 | 
            +
                  end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                  it "has the docstring" do
         | 
| 124 | 
            +
                    expect(c.method_struct(method(:quux)).docstring).to eq("First line,\n" +
         | 
| 125 | 
            +
                                                                           "second line.\n")
         | 
| 126 | 
            +
                  end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                  it "has the source" do
         | 
| 129 | 
            +
                    expect(c.method_struct(method(:quux)).source).to eq("def quux(n)\nend\n")
         | 
| 130 | 
            +
                  end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                  it "has no aliases" do
         | 
| 133 | 
            +
                    expect(c.method_struct(method(:quux)).aliases).to eq([])
         | 
| 134 | 
            +
                  end
         | 
| 135 | 
            +
                end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                it "should return the source for one-line methods" do
         | 
| 138 | 
            +
                  def xuuq(); end
         | 
| 139 | 
            +
                  expect(c.method_struct(method(:xuuq)).source).to eq("def xuuq(); end\n")
         | 
| 140 | 
            +
                end
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                it "should return empty docstring when none" do
         | 
| 143 | 
            +
                  def xuuq(m)
         | 
| 144 | 
            +
                  end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                  struct = c.method_struct(method(:xuuq))
         | 
| 147 | 
            +
                  expect(struct.docstring).to eq("")
         | 
| 148 | 
            +
                  expect(struct.source).not_to be_empty
         | 
| 149 | 
            +
                end
         | 
| 150 | 
            +
              end
         | 
| 151 | 
            +
             | 
| 152 | 
            +
              context "dynamically defined" do
         | 
| 153 | 
            +
                let(:kls) { eval "Class.new do;def foo;42;end;end" }
         | 
| 154 | 
            +
                let(:c) { described_class }
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                it "returns no docstring" do
         | 
| 157 | 
            +
                  expect(c.method_struct(kls.instance_method(:foo)).docstring).to be_empty
         | 
| 158 | 
            +
                end
         | 
| 159 | 
            +
             | 
| 160 | 
            +
                it "returns comment about dynamic definition as the source" do
         | 
| 161 | 
            +
                  expect(c.method_struct(kls.instance_method(:foo)).source)
         | 
| 162 | 
            +
                    .to include("outside of a source file")
         | 
| 163 | 
            +
                end
         | 
| 164 | 
            +
              end
         | 
| 165 | 
            +
            end
         |