@@ -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 )
0 commit comments