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,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'robe/sash/includes_tracker'
|
3
|
+
|
4
|
+
describe Robe::Sash::IncludesTracker do
|
5
|
+
klass = described_class
|
6
|
+
|
7
|
+
let(:m) { Module.new { def foo; end } }
|
8
|
+
let(:names) { Names.new }
|
9
|
+
|
10
|
+
context "after change in environment" do
|
11
|
+
before do
|
12
|
+
expect(klass.method_owner_and_inst(m, names)).to eq([nil, true])
|
13
|
+
end
|
14
|
+
|
15
|
+
it "detects an included module" do
|
16
|
+
n = Module.new
|
17
|
+
stub_const("N", n)
|
18
|
+
n.send(:include, m)
|
19
|
+
expect(klass.method_owner_and_inst(m, names)).to eq(["N", true])
|
20
|
+
end
|
21
|
+
|
22
|
+
it "detects an extended module" do
|
23
|
+
n = Module.new
|
24
|
+
stub_const("N", n)
|
25
|
+
n.send(:extend, m)
|
26
|
+
expect(klass.method_owner_and_inst(m, names)).to eq(["N", nil])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Names
|
31
|
+
def [](mod)
|
32
|
+
mod.__name__
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,420 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'support/mocks'
|
3
|
+
require 'robe/sash'
|
4
|
+
|
5
|
+
describe Robe::Sash do
|
6
|
+
klass = described_class
|
7
|
+
|
8
|
+
context "#modules" do
|
9
|
+
it "returns module names" do
|
10
|
+
mock_space = BlindVisor.new(*%w(A B C).map { |n| OpenStruct.new(__name__: n) })
|
11
|
+
expect(klass.new(mock_space).modules).to eq %w(A B C)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context "#class_locations" do
|
16
|
+
it "shows location when class has methods" do
|
17
|
+
k = klass.new(double(resolve_context: Class.new { def foo; end }))
|
18
|
+
expect(k.class_locations(nil, nil)).to eq([__FILE__])
|
19
|
+
end
|
20
|
+
|
21
|
+
it "shows no location for class without methods" do
|
22
|
+
k = klass.new(double(resolve_context: Class.new))
|
23
|
+
expect(k.class_locations(nil, nil)).to be_empty
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "#targets" do
|
28
|
+
context "value is a module" do
|
29
|
+
let(:m) do
|
30
|
+
Module.new do
|
31
|
+
def foo; end
|
32
|
+
private
|
33
|
+
def baz; end
|
34
|
+
class << self
|
35
|
+
alias_method :inspect, :name
|
36
|
+
def oom; end
|
37
|
+
private
|
38
|
+
def tee; end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
before { stub_const("M", m) }
|
44
|
+
|
45
|
+
let(:k) { klass.new }
|
46
|
+
|
47
|
+
subject { k.targets("M")[1..-1] }
|
48
|
+
|
49
|
+
specify { expect(k.targets("M")[0]).to eq("M") }
|
50
|
+
|
51
|
+
it { should include_spec("M#foo") }
|
52
|
+
it { should include_spec("M#baz") }
|
53
|
+
it { should include_spec("M.oom") }
|
54
|
+
it { expect(subject.select { |(_, _, m)| m == :tee }).to be_empty }
|
55
|
+
end
|
56
|
+
|
57
|
+
it "looks at the class if the value is not a module" do
|
58
|
+
expect(klass.new.targets("Math::E")).to include_spec("Float#ceil")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "#find_method" do
|
63
|
+
let(:k) { klass.new }
|
64
|
+
|
65
|
+
it { expect(k.find_method(String, true, :gsub).name).to eq(:gsub) }
|
66
|
+
it { expect(k.find_method(String, nil, :freeze).name).to eq(:freeze) }
|
67
|
+
end
|
68
|
+
|
69
|
+
context "#find_method_owner" do
|
70
|
+
let(:k) { klass.new }
|
71
|
+
|
72
|
+
it { expect(k.find_method_owner(File, nil, :open)).to eq(IO.singleton_class)}
|
73
|
+
it { expect(k.find_method_owner(String, true, :split)).to eq(String)}
|
74
|
+
it { expect(k.find_method_owner(Class.new, nil, :boo)).to be_nil}
|
75
|
+
end
|
76
|
+
|
77
|
+
context "#method_spec" do
|
78
|
+
let(:k) { klass.new }
|
79
|
+
|
80
|
+
it "works on String#gsub" do
|
81
|
+
match = if RUBY_ENGINE == "rbx"
|
82
|
+
start_with("String", true, :gsub)
|
83
|
+
else
|
84
|
+
eq(["String", true, :gsub, [[:rest]]])
|
85
|
+
end
|
86
|
+
expect(k.method_spec(String.instance_method(:gsub))).to match
|
87
|
+
end
|
88
|
+
|
89
|
+
it "includes method location" do
|
90
|
+
m = Module.new { def foo; end }
|
91
|
+
expect(k.method_spec(m.instance_method(:foo))[4..5])
|
92
|
+
.to eq([__FILE__, __LINE__ - 2])
|
93
|
+
end
|
94
|
+
|
95
|
+
it "includes method parameters" do
|
96
|
+
m = Module.new { def foo(a, *b, &c); end }
|
97
|
+
expect(k.method_spec(m.instance_method(:foo)))
|
98
|
+
.to eq([nil, true, :foo, [[:req, :a], [:rest, :b], [:block, :c]],
|
99
|
+
__FILE__, anything])
|
100
|
+
end
|
101
|
+
|
102
|
+
it "ignores overridden name method" do
|
103
|
+
# Celluloid::Actor in celluloid <~ 0.15
|
104
|
+
# https://github.com/celluloid/celluloid/issues/354
|
105
|
+
|
106
|
+
m = Module.new do
|
107
|
+
def self.name
|
108
|
+
raise TooCoolForSchoolError
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.__name__
|
112
|
+
"baa"
|
113
|
+
end
|
114
|
+
|
115
|
+
def qux
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
expect(k.method_spec(m.instance_method(:qux)))
|
120
|
+
.to eq(["baa", true, :qux, [], __FILE__, anything])
|
121
|
+
end
|
122
|
+
|
123
|
+
context "eigenclass" do
|
124
|
+
let(:c) do
|
125
|
+
Class.new do
|
126
|
+
class << self
|
127
|
+
def foo; end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
it "substitutes eigenclass with the actual class name" do
|
133
|
+
stub_const("M::C", c)
|
134
|
+
expect(k.method_spec(c.singleton_class.instance_method(:foo))[0])
|
135
|
+
.to eq("M::C")
|
136
|
+
end
|
137
|
+
|
138
|
+
it "skips anonymous one" do
|
139
|
+
expect(k.method_spec(c.singleton_class.instance_method(:foo))[0])
|
140
|
+
.to be_nil
|
141
|
+
end
|
142
|
+
|
143
|
+
it "recognizes ActiveRecord classes" do
|
144
|
+
arc = Class.new do
|
145
|
+
class << self
|
146
|
+
def bar; end
|
147
|
+
def inspect
|
148
|
+
"Record(id: integer)"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
stub_const("Record", arc)
|
154
|
+
|
155
|
+
expect(k.method_spec(arc.singleton_class.instance_method(:bar))[0]).to eq("Record")
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context "anonymous owner" do
|
160
|
+
let(:m) { Module.new { def foo; end} }
|
161
|
+
|
162
|
+
it "returns nil first element" do
|
163
|
+
expect(k.method_spec(m.instance_method(:foo))[0]).to be_nil
|
164
|
+
end
|
165
|
+
|
166
|
+
it "substitutes anonymous module with including class name" do
|
167
|
+
stub_const("C", Class.new.send(:include, m) )
|
168
|
+
spec = k.method_spec(m.instance_method(:foo))
|
169
|
+
expect(spec[0]).to eq("C")
|
170
|
+
expect(spec[1]).to eq(true)
|
171
|
+
end
|
172
|
+
|
173
|
+
it "substitutes anonymous modules with extending module name" do
|
174
|
+
stub_const("M", Module.new.send(:extend, m) )
|
175
|
+
spec = k.method_spec(m.instance_method(:foo))
|
176
|
+
expect(spec[0]).to eq("M")
|
177
|
+
expect(spec[1]).to eq(nil)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
context "#doc_for" do
|
183
|
+
it "returns doc hash for instance method" do
|
184
|
+
k = klass.new
|
185
|
+
hash = k.doc_for("Set", true, "replace")
|
186
|
+
expect(hash[:docstring]).to start_with("Replaces the contents")
|
187
|
+
end
|
188
|
+
|
189
|
+
it "returns doc hash for module method" do
|
190
|
+
k = klass.new
|
191
|
+
hash = k.doc_for("Set", nil, "[]")
|
192
|
+
expect(hash[:docstring]).to start_with("Creates a new set containing")
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
context "#method_targets" do
|
197
|
+
it "returns empty array when not found" do
|
198
|
+
k = klass.new(ScopedVisor.new)
|
199
|
+
expect(k.method_targets("a", "b", "c", true, nil, nil)).to be_empty
|
200
|
+
end
|
201
|
+
|
202
|
+
context "examples" do
|
203
|
+
let(:k) { klass.new }
|
204
|
+
|
205
|
+
it "returns class method candidate" do
|
206
|
+
expect(k.method_targets("open", "File", nil, nil, nil, nil))
|
207
|
+
.to have_one_spec("IO.open")
|
208
|
+
end
|
209
|
+
|
210
|
+
it "returns the method on Class" do
|
211
|
+
expect(k.method_targets("superclass", "Object", nil, nil, nil, nil))
|
212
|
+
.to have_one_spec("Class#superclass")
|
213
|
+
end
|
214
|
+
|
215
|
+
it "returns the non-overridden method" do
|
216
|
+
targets = k.method_targets("new", "Object", nil, nil, nil, nil)
|
217
|
+
expect(targets).to include_spec("BasicObject#initialize")
|
218
|
+
expect(targets).not_to include_spec("Class#new")
|
219
|
+
end
|
220
|
+
|
221
|
+
it "returns #new overridden in the given class" do
|
222
|
+
c = Class.new do
|
223
|
+
def self.new
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
stub_const("C", c)
|
228
|
+
|
229
|
+
targets = k.method_targets("new", "C", nil, nil, nil, nil)
|
230
|
+
expect(targets).to include_spec("C.new")
|
231
|
+
expect(targets).not_to include_spec("Class#new")
|
232
|
+
expect(targets).not_to include_spec("BasicObject#initialize")
|
233
|
+
end
|
234
|
+
|
235
|
+
it "doesn't return overridden method" do
|
236
|
+
expect(k.method_targets("to_s", "Hash", nil, true, nil, nil))
|
237
|
+
.to have_one_spec("Hash#to_s")
|
238
|
+
end
|
239
|
+
|
240
|
+
context "unknown target" do
|
241
|
+
it "returns String method candidate" do
|
242
|
+
expect(k.method_targets("split", "s", nil, true, nil, nil))
|
243
|
+
.to include_spec("String#split")
|
244
|
+
end
|
245
|
+
|
246
|
+
it "does not return wrong candidates" do
|
247
|
+
candidates = k.method_targets("split", "s", nil, true, nil, nil)
|
248
|
+
expect(candidates).to be_all { |c| c[2] == :split }
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
it "returns no candidates for target when conservative" do
|
253
|
+
expect(k.method_targets("split", nil, nil, true, nil, true))
|
254
|
+
.to be_empty
|
255
|
+
end
|
256
|
+
|
257
|
+
it "returns single instance method from superclass" do
|
258
|
+
expect(k.method_targets("map", nil, "Array", true, true, nil))
|
259
|
+
.to have_one_spec("Enumerable#map")
|
260
|
+
end
|
261
|
+
|
262
|
+
it "returns single method from target class" do
|
263
|
+
expect(k.method_targets("map", nil, "Array", true, nil, nil))
|
264
|
+
.to have_one_spec("Array#map")
|
265
|
+
end
|
266
|
+
|
267
|
+
it "checks for instance Kernel methods when the target is a module" do
|
268
|
+
# Not 100% accurate: the including class may derive from BasicObject
|
269
|
+
stub_const("M", Module.new)
|
270
|
+
expect(k.method_targets("puts", nil, "M", true, nil, true))
|
271
|
+
.to have_one_spec("Kernel#puts")
|
272
|
+
end
|
273
|
+
|
274
|
+
it "checks private Kernel methods when no primary candidates" do
|
275
|
+
k = klass.new(BlindVisor.new)
|
276
|
+
expect(k.method_targets("puts", nil, nil, true, nil, nil))
|
277
|
+
.to have_one_spec("Kernel#puts")
|
278
|
+
end
|
279
|
+
|
280
|
+
it "sorts results list" do
|
281
|
+
extend ScannerHelper
|
282
|
+
|
283
|
+
a = named_module("A", "a", "b", "c", "d")
|
284
|
+
b = named_module("A::B", "a", "b", "c", "d")
|
285
|
+
c = new_module("a", "b", "c", "d")
|
286
|
+
k = klass.new(ScopedVisor.new(*[b, c, a].shuffle))
|
287
|
+
expect(k.method_targets("a", nil, nil, true, nil, nil).map(&:first))
|
288
|
+
.to eq(["A", "A::B", nil])
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
context "#complete_method" do
|
294
|
+
let(:k) { klass.new }
|
295
|
+
|
296
|
+
it "completes instance methods" do
|
297
|
+
expect(k.complete_method("gs", nil, nil, true))
|
298
|
+
.to include_spec("String#gsub", "String#gsub!")
|
299
|
+
end
|
300
|
+
|
301
|
+
context "class methods" do
|
302
|
+
let(:k) { klass.new(ScopedVisor.new(Class, {"Object" => Object})) }
|
303
|
+
|
304
|
+
it "completes public" do
|
305
|
+
expect(k.complete_method("su", nil, nil, nil))
|
306
|
+
.to include_spec("Class#superclass")
|
307
|
+
end
|
308
|
+
|
309
|
+
it "no private methods with explicit target" do
|
310
|
+
expect(k.complete_method("attr", "Object", nil, nil))
|
311
|
+
.not_to include_spec("Module#attr_reader")
|
312
|
+
end
|
313
|
+
|
314
|
+
it "no private methods with no target at all" do
|
315
|
+
expect(k.complete_method("attr", "Object", nil, nil))
|
316
|
+
.not_to include_spec("Module#attr_reader")
|
317
|
+
end
|
318
|
+
|
319
|
+
it "completes private methods with implicit target" do
|
320
|
+
expect(k.complete_method("attr", nil, "Object", nil))
|
321
|
+
.to include_spec("Module#attr_reader", "Module#attr_writer")
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
context "#complete_const" do
|
327
|
+
let(:m) do
|
328
|
+
Module.new do
|
329
|
+
self::ACONST = 1
|
330
|
+
|
331
|
+
module self::AMOD; end
|
332
|
+
module self::BMOD
|
333
|
+
module self::C; end
|
334
|
+
end
|
335
|
+
|
336
|
+
class self::ACLS; end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
let(:v) { ScopedVisor.new({"Test" => m})}
|
340
|
+
let(:k) { klass.new(v) }
|
341
|
+
|
342
|
+
context "sandboxed" do
|
343
|
+
it "completes all constants" do
|
344
|
+
expect(k.complete_const("Test::A", nil))
|
345
|
+
.to match_array(%w(Test::ACONST Test::AMOD Test::ACLS))
|
346
|
+
end
|
347
|
+
|
348
|
+
it "requires names to begin with prefix" do
|
349
|
+
expect(k.complete_const("Test::MOD", nil)).to be_empty
|
350
|
+
end
|
351
|
+
|
352
|
+
it "completes the constant in the nesting" do
|
353
|
+
expect(k.complete_const("A", "Test")).to include("ACONST")
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
it "completes with bigger nesting" do
|
358
|
+
expect(k.complete_const("BMOD::C", "Test")).to eq(["BMOD::C"])
|
359
|
+
end
|
360
|
+
|
361
|
+
it "completes global constants" do
|
362
|
+
expect(k.complete_const("Ob", nil)).to include("Object", "ObjectSpace")
|
363
|
+
end
|
364
|
+
|
365
|
+
it "completes the constants in all containing scopes" do
|
366
|
+
k = klass.new
|
367
|
+
expect(k.complete_const("C", "Encoding"))
|
368
|
+
.to include("Converter", "Class", "Complex")
|
369
|
+
end
|
370
|
+
|
371
|
+
it "uses the full access path from the request" do
|
372
|
+
k = klass.new
|
373
|
+
expect(k.complete_const("Object::File::S", nil)).to include("Object::File::Stat")
|
374
|
+
end
|
375
|
+
|
376
|
+
it "keeps the global qualifier" do
|
377
|
+
k = klass.new
|
378
|
+
expect(k.complete_const("::Obj", nil)).to match_array(["::Object", "::ObjectSpace"])
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
context "#load_path" do
|
383
|
+
it 'returns an appropriate value' do
|
384
|
+
expect(klass.new.load_path).to eq($:)
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
it { expect(klass.new.ping).to be_true }
|
389
|
+
|
390
|
+
RSpec::Matchers.define :include_spec do |*specs|
|
391
|
+
match do |candidates|
|
392
|
+
actual = candidates.map { |mod, instance, sym|
|
393
|
+
MethodSpec.to_str(mod, instance, sym)
|
394
|
+
}
|
395
|
+
RSpec::Matchers::BuiltIn::Include.new(*specs).matches?(actual)
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
RSpec::Matchers.define :have_one_spec do |spec|
|
400
|
+
match do |candidates|
|
401
|
+
candidates.length == 1 and
|
402
|
+
MethodSpec.new(spec) == candidates[0]
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
class MethodSpec
|
407
|
+
def initialize(str)
|
408
|
+
@str = str
|
409
|
+
end
|
410
|
+
|
411
|
+
def self.to_str(mod, inst, sym)
|
412
|
+
"#{mod}#{inst ? '#' : '.'}#{sym}"
|
413
|
+
end
|
414
|
+
|
415
|
+
def ==(other)
|
416
|
+
other.is_a?(Array) && other.length > 2 &&
|
417
|
+
self.class.to_str(*other[0..2]) == @str
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|