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
|