Class: Decode::Language::Ruby::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/decode/language/ruby/parser.rb

Overview

The Ruby source code parser.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(language) ⇒ Parser

Initialize a new Ruby parser.



29
30
31
32
33
34
# File 'lib/decode/language/ruby/parser.rb', line 29

def initialize(language)
	@language = language
	
	@visibility = :public
	@definitions = Hash.new.compare_by_identity
end

Instance Attribute Details

#definitionsObject (readonly)

Returns the value of attribute definitions.



43
44
45
# File 'lib/decode/language/ruby/parser.rb', line 43

def definitions
  @definitions
end

#languageObject (readonly)

Returns the value of attribute language.



37
38
39
# File 'lib/decode/language/ruby/parser.rb', line 37

def language
  @language
end

#The language instance.(languageinstance.) ⇒ Object (readonly)



37
# File 'lib/decode/language/ruby/parser.rb', line 37

attr :language

#visibilityObject (readonly)

Returns the value of attribute visibility.



40
41
42
# File 'lib/decode/language/ruby/parser.rb', line 40

def visibility
  @visibility
end

Instance Method Details

#Cache for definition lookups.=Object



43
# File 'lib/decode/language/ruby/parser.rb', line 43

attr :definitions

#definitions_for(source, &block) ⇒ Object

Extract definitions from the given input file.



56
57
58
59
60
61
62
63
64
65
# File 'lib/decode/language/ruby/parser.rb', line 56

def definitions_for(source, &block)
	return enum_for(:definitions_for, source) unless block_given?
	
	result = self.parse_source(source)
	result.attach_comments!
	
	# Pass the source to walk_definitions for location tracking
	source = source.is_a?(Source) ? source : nil
	walk_definitions(result.value, nil, source, &block)
end

#segments_for(source, &block) ⇒ Object

Extract segments from the given input file.



313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/decode/language/ruby/parser.rb', line 313

def segments_for(source, &block)
	result = self.parse_source(source)
	comments = result.comments.reject do |comment|
		comment.location.slice.start_with?("#!/") || 
				comment.location.slice.start_with?("# frozen_string_literal:") ||
				comment.location.slice.start_with?("# Released under the MIT License.") ||
				comment.location.slice.start_with?("# Copyright,")
	end
	
	# Now we iterate over the syntax tree and generate segments:
	walk_segments(result.value, comments, &block)
end

#The current visibility mode.=(currentvisibilitymode. = (value)) ⇒ Object



40
# File 'lib/decode/language/ruby/parser.rb', line 40

attr :visibility

#walk_definitions(node, parent = nil, source = nil, &block) ⇒ Object

Walk over the syntax tree and extract relevant definitions with their associated comments.



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/decode/language/ruby/parser.rb', line 71

def walk_definitions(node, parent = nil, source = nil, &block)
	# Check for scope definitions from comments
	if node.comments.any?
		parent = scope_for(comments_for(node), parent, &block) || parent
	end
	
	case node.type
	when :program_node
		with_visibility do
			node.child_nodes.each do |child|
				walk_definitions(child, parent, source, &block)
			end
		end
	when :statements_node
		node.child_nodes.each do |child|
			walk_definitions(child, parent, source, &block)
		end
	when :block_node
		if node.body
			walk_definitions(node.body, parent, source, &block)
		end
	when :module_node
		path = nested_path_for(node.constant_path)
		
		definition = Module.new(path,
					visibility: :public,
					comments: comments_for(node),
					parent: parent,
					node: node,
					language: @language,
					source: source,
				)
		
		store_definition(parent, path.last.to_sym, definition)
		yield definition
		
		if body = node.body
			with_visibility do
				walk_definitions(body, definition, source, &block)
			end
		end
	when :class_node
		path = nested_path_for(node.constant_path)
		super_class = nested_name_for(node.superclass)
		
		definition = Class.new(path,
					super_class: super_class,
					visibility: :public, 
					comments: comments_for(node),
					parent: parent,
					node: node,
					language: @language,
					source: source,
				)
		
		store_definition(parent, path.last.to_sym, definition)
		yield definition
		
		if body = node.body
			with_visibility do
				walk_definitions(body, definition, source, &block)
			end
		end
	when :singleton_class_node
		if name = singleton_name_for(node)
			definition = Singleton.new(name,
						comments: comments_for(node),
						parent: parent,
						node: node,
						language: @language,
						visibility: :public,
						source: source
					)
			
			yield definition
			
			if body = node.body
				walk_definitions(body, definition, source, &block)
			end
		end
	when :def_node
		receiver = receiver_for(node.receiver)
		
		definition = Method.new(node.name,
					visibility: @visibility,
					comments: comments_for(node),
					parent: parent,
					node: node,
					language: @language,
					receiver: receiver,
					source: source,
				)
		
		yield definition
	when :constant_write_node
		definition = Constant.new(node.name,
					comments: comments_for(node),
					parent: parent,
					node: node,
					language: @language,
				)
		
		store_definition(parent, node.name, definition)
		yield definition
	when :call_node
		name = node.name
		
		case name
		when :public, :protected, :private
			# Handle cases like "private def foo" where method definitions are arguments
			if node.arguments
				has_method_definitions = false
				node.arguments.arguments.each do |argument_node|
					if argument_node.type == :def_node
						has_method_definitions = true
						# Process the method definition with the specified visibility
						receiver = receiver_for(argument_node.receiver)
						
						definition = Method.new(argument_node.name,
									visibility: name,
									comments: comments_for(argument_node),
									parent: parent,
									node: argument_node,
									language: @language,
									receiver: receiver,
								)
						
						yield definition
					end
				end
				
				# Only set visibility state if this is NOT an inline method definition
				unless has_method_definitions
					@visibility = name
				end
			else
				# No arguments, so this is a standalone visibility modifier
				@visibility = name
			end
		when :private_constant
			if node.arguments
				constant_names_for(node.arguments.arguments) do |name|
					if definition = lookup_definition(parent, name)
						definition.visibility = :private
					end
				end
			end
		when :attr, :attr_reader, :attr_writer, :attr_accessor
			definition = Attribute.new(attribute_name_for(node),
						comments: comments_for(node),
						parent: parent, language: @language, node: node
					)
			
			yield definition
		when :alias_method
			# Handle alias_method :new_name, :old_name syntax
			if node.arguments && node.arguments.arguments.size >= 2
				new_name_arg = node.arguments.arguments[0]
				old_name_arg = node.arguments.arguments[1]
				
				# Extract symbol names from the arguments
				new_name = symbol_name_for(new_name_arg)
				old_name = symbol_name_for(old_name_arg)
				
				definition = Alias.new(new_name.to_sym, old_name.to_sym,
							comments: comments_for(node),
							parent: parent,
							node: node,
							language: @language,
							visibility: @visibility,
							source: source,
						)
				
				yield definition
			end
		else
			# Check if this call should be treated as a definition
			# either because it has a @name comment, @attribute comment, or a block
			has_name_comment = comments_for(node).any? {|comment| comment.match(NAME_ATTRIBUTE)}
			has_attribute_comment = kind_for(node, comments_for(node))
			has_block = node.block
			
			if has_name_comment || has_attribute_comment || has_block
				definition = Call.new(
							attribute_name_for(node),
							comments: comments_for(node),
							parent: parent, language: @language, node: node
						)
				
				yield definition
				
				# Walk into the block body if it exists
				if node.block
					walk_definitions(node.block, definition, source, &block)
				end
			end
		end
	when :alias_method_node
		# Handle alias new_name old_name syntax
		new_name = node.new_name.unescaped
		old_name = node.old_name.unescaped
		
		definition = Alias.new(new_name.to_sym, old_name.to_sym,
					comments: comments_for(node),
					parent: parent,
					node: node,
					language: @language,
					visibility: @visibility,
					source: source,
				)
		
		yield definition
	when :if_node
		# Walk the 'if' branch (statements):
		if node.statements
			walk_definitions(node.statements, parent, source, &block)
		end
		# Walk the 'else' or 'elsif' branch (subsequent):
		if node.subsequent
			walk_definitions(node.subsequent, parent, source, &block)
		end
	when :unless_node
		# Walk the 'unless' branch (statements):
		if node.statements
			walk_definitions(node.statements, parent, source, &block)
		end
		# Walk the 'else' branch (else_clause):
		if node.else_clause
			walk_definitions(node.else_clause, parent, source, &block)
		end
	else
		if node.respond_to?(:statements)
			walk_definitions(node.statements, parent, source, &block)
		else
			# $stderr.puts "Ignoring #{node.type}"
		end
	end
end