Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 4d65988

Browse files
committed
Comments on index entries
1 parent 735da70 commit 4d65988

File tree

2 files changed

+195
-22
lines changed

2 files changed

+195
-22
lines changed

lib/syntax_tree/index.rb

Lines changed: 174 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,46 +20,128 @@ def initialize(line, column)
2020

2121
# This entry represents a class definition using the class keyword.
2222
class ClassDefinition
23-
attr_reader :nesting, :name, :location
23+
attr_reader :nesting, :name, :location, :comments
2424

25-
def initialize(nesting, name, location)
25+
def initialize(nesting, name, location, comments)
2626
@nesting = nesting
2727
@name = name
2828
@location = location
29+
@comments = comments
2930
end
3031
end
3132

3233
# This entry represents a module definition using the module keyword.
3334
class ModuleDefinition
34-
attr_reader :nesting, :name, :location
35+
attr_reader :nesting, :name, :location, :comments
3536

36-
def initialize(nesting, name, location)
37+
def initialize(nesting, name, location, comments)
3738
@nesting = nesting
3839
@name = name
3940
@location = location
41+
@comments = comments
4042
end
4143
end
4244

4345
# This entry represents a method definition using the def keyword.
4446
class MethodDefinition
45-
attr_reader :nesting, :name, :location
47+
attr_reader :nesting, :name, :location, :comments
4648

47-
def initialize(nesting, name, location)
49+
def initialize(nesting, name, location, comments)
4850
@nesting = nesting
4951
@name = name
5052
@location = location
53+
@comments = comments
5154
end
5255
end
5356

5457
# This entry represents a singleton method definition using the def keyword
5558
# with a specified target.
5659
class SingletonMethodDefinition
57-
attr_reader :nesting, :name, :location
60+
attr_reader :nesting, :name, :location, :comments
5861

59-
def initialize(nesting, name, location)
62+
def initialize(nesting, name, location, comments)
6063
@nesting = nesting
6164
@name = name
6265
@location = location
66+
@comments = comments
67+
end
68+
end
69+
70+
# When you're using the instruction sequence backend, this class is used to
71+
# lazily parse comments out of the source code.
72+
class FileComments
73+
# We use the ripper library to pull out source comments.
74+
class Parser < Ripper
75+
attr_reader :comments
76+
77+
def initialize(*)
78+
super
79+
@comments = {}
80+
end
81+
82+
def on_comment(value)
83+
comments[lineno] = value.chomp
84+
end
85+
end
86+
87+
# This represents the Ruby source in the form of a file. When it needs to
88+
# be read we'll read the file.
89+
class FileSource
90+
attr_reader :filepath
91+
92+
def initialize(filepath)
93+
@filepath = filepath
94+
end
95+
96+
def source
97+
File.read(filepath)
98+
end
99+
end
100+
101+
# This represents the Ruby source in the form of a string. When it needs
102+
# to be read the string is returned.
103+
class StringSource
104+
attr_reader :source
105+
106+
def initialize(source)
107+
@source = source
108+
end
109+
end
110+
111+
attr_reader :source
112+
113+
def initialize(source)
114+
@source = source
115+
end
116+
117+
def comments
118+
@comments ||= Parser.new(source.source).tap(&:parse).comments
119+
end
120+
end
121+
122+
# This class handles parsing comments from Ruby source code in the case that
123+
# we use the instruction sequence backend. Because the instruction sequence
124+
# backend doesn't provide comments (since they are dropped) we provide this
125+
# interface to lazily parse them out.
126+
class EntryComments
127+
include Enumerable
128+
attr_reader :file_comments, :location
129+
130+
def initialize(file_comments, location)
131+
@file_comments = file_comments
132+
@location = location
133+
end
134+
135+
def each(&block)
136+
line = location.line - 1
137+
result = []
138+
139+
while line >= 0 && (comment = file_comments.comments[line])
140+
result.unshift(comment)
141+
line -= 1
142+
end
143+
144+
result.each(&block)
63145
end
64146
end
65147

@@ -74,16 +156,22 @@ class ISeqBackend
74156
VM_DEFINECLASS_FLAG_HAS_SUPERCLASS = 0x10
75157

76158
def index(source)
77-
index_iseq(RubyVM::InstructionSequence.compile(source).to_a)
159+
index_iseq(
160+
RubyVM::InstructionSequence.compile(source).to_a,
161+
FileComments.new(FileComments::StringSource.new(source))
162+
)
78163
end
79164

80165
def index_file(filepath)
81-
index_iseq(RubyVM::InstructionSequence.compile_file(filepath).to_a)
166+
index_iseq(
167+
RubyVM::InstructionSequence.compile_file(filepath).to_a,
168+
FileComments.new(FileComments::FileSource.new(filepath))
169+
)
82170
end
83171

84172
private
85173

86-
def index_iseq(iseq)
174+
def index_iseq(iseq, file_comments)
87175
results = []
88176
queue = [[iseq, []]]
89177

@@ -106,11 +194,23 @@ def index_iseq(iseq)
106194
elsif flags & VM_DEFINECLASS_TYPE_MODULE > 0
107195
code_location = class_iseq[4][:code_location]
108196
location = Location.new(code_location[0], code_location[1])
109-
results << ModuleDefinition.new(current_nesting, name, location)
197+
198+
results << ModuleDefinition.new(
199+
current_nesting,
200+
name,
201+
location,
202+
EntryComments.new(file_comments, location)
203+
)
110204
else
111205
code_location = class_iseq[4][:code_location]
112206
location = Location.new(code_location[0], code_location[1])
113-
results << ClassDefinition.new(current_nesting, name, location)
207+
208+
results << ClassDefinition.new(
209+
current_nesting,
210+
name,
211+
location,
212+
EntryComments.new(file_comments, location)
213+
)
114214
end
115215

116216
queue << [class_iseq, current_nesting + [name]]
@@ -122,14 +222,21 @@ def index_iseq(iseq)
122222
results << SingletonMethodDefinition.new(
123223
current_nesting,
124224
name,
125-
location
225+
location,
226+
EntryComments.new(file_comments, location)
126227
)
127228
when :definesmethod
128229
_, name, method_iseq = insn
129230

130231
code_location = method_iseq[4][:code_location]
131232
location = Location.new(code_location[0], code_location[1])
132-
results << MethodDefinition.new(current_nesting, name, location)
233+
234+
results << MethodDefinition.new(
235+
current_nesting,
236+
name,
237+
location,
238+
EntryComments.new(file_comments, location)
239+
)
133240
end
134241
end
135242
end
@@ -143,21 +250,27 @@ def index_iseq(iseq)
143250
# supported on all runtimes.
144251
class ParserBackend
145252
class IndexVisitor < Visitor
146-
attr_reader :results, :nesting
253+
attr_reader :results, :nesting, :statements
147254

148255
def initialize
149256
@results = []
150257
@nesting = []
258+
@statements = nil
151259
end
152260

153261
def visit_class(node)
154262
name = visit(node.constant).to_sym
155263
location =
156264
Location.new(node.location.start_line, node.location.start_column)
157265

158-
results << ClassDefinition.new(nesting.dup, name, location)
159-
nesting << name
266+
results << ClassDefinition.new(
267+
nesting.dup,
268+
name,
269+
location,
270+
comments_for(node)
271+
)
160272

273+
nesting << name
161274
super
162275
nesting.pop
163276
end
@@ -172,9 +285,19 @@ def visit_def(node)
172285
Location.new(node.location.start_line, node.location.start_column)
173286

174287
results << if node.target.nil?
175-
MethodDefinition.new(nesting.dup, name, location)
288+
MethodDefinition.new(
289+
nesting.dup,
290+
name,
291+
location,
292+
comments_for(node)
293+
)
176294
else
177-
SingletonMethodDefinition.new(nesting.dup, name, location)
295+
SingletonMethodDefinition.new(
296+
nesting.dup,
297+
name,
298+
location,
299+
comments_for(node)
300+
)
178301
end
179302
end
180303

@@ -183,9 +306,14 @@ def visit_module(node)
183306
location =
184307
Location.new(node.location.start_line, node.location.start_column)
185308

186-
results << ModuleDefinition.new(nesting.dup, name, location)
187-
nesting << name
309+
results << ModuleDefinition.new(
310+
nesting.dup,
311+
name,
312+
location,
313+
comments_for(node)
314+
)
188315

316+
nesting << name
189317
super
190318
nesting.pop
191319
end
@@ -194,6 +322,30 @@ def visit_program(node)
194322
super
195323
results
196324
end
325+
326+
def visit_statements(node)
327+
@statements = node
328+
super
329+
end
330+
331+
private
332+
333+
def comments_for(node)
334+
comments = []
335+
336+
body = statements.body
337+
line = node.location.start_line - 1
338+
index = body.index(node) - 1
339+
340+
while index >= 0 && body[index].is_a?(Comment) &&
341+
(line - body[index].location.start_line < 2)
342+
comments.unshift(body[index].value)
343+
line = body[index].location.start_line
344+
index -= 1
345+
end
346+
347+
comments
348+
end
197349
end
198350

199351
def index(source)

test/index_test.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ def test_module_nested
1818
end
1919
end
2020

21+
def test_module_comments
22+
index_each("# comment1\n# comment2\nmodule Foo; end") do |entry|
23+
assert_equal :Foo, entry.name
24+
assert_equal ["# comment1", "# comment2"], entry.comments.to_a
25+
end
26+
end
27+
2128
def test_class
2229
index_each("class Foo; end") do |entry|
2330
assert_equal :Foo, entry.name
@@ -32,6 +39,13 @@ def test_class_nested
3239
end
3340
end
3441

42+
def test_class_comments
43+
index_each("# comment1\n# comment2\nclass Foo; end") do |entry|
44+
assert_equal :Foo, entry.name
45+
assert_equal ["# comment1", "# comment2"], entry.comments.to_a
46+
end
47+
end
48+
3549
def test_method
3650
index_each("def foo; end") do |entry|
3751
assert_equal :foo, entry.name
@@ -46,6 +60,13 @@ def test_method_nested
4660
end
4761
end
4862

63+
def test_method_comments
64+
index_each("# comment1\n# comment2\ndef foo; end") do |entry|
65+
assert_equal :foo, entry.name
66+
assert_equal ["# comment1", "# comment2"], entry.comments.to_a
67+
end
68+
end
69+
4970
private
5071

5172
def index_each(source)

0 commit comments

Comments
 (0)