robe-server 1.0.2

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