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,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
|