robe-server 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|