Skip to content

Commit 115bb79

Browse files
committed
first cut rbi generator
1 parent 10eda01 commit 115bb79

File tree

3 files changed

+80
-18
lines changed

3 files changed

+80
-18
lines changed

lib/rbi_generator/generator.rb

Lines changed: 68 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,79 @@ def initialize(file_path)
1313

1414
def generate
1515
content = File.read(@file_path)
16-
ast = Ripper.sexp(content)
17-
pp ast
18-
class_name = extract_class_name(ast)
19-
method_names = extract_method_names(ast)
20-
puts "Class Name: #{class_name}"
21-
puts "Method Names: #{method_names.join(", ")}"
22-
# puts "Method Names: #{method_names.join(", ")}"
23-
# puts Ripper.sexp(content)
16+
result = Prism.parse(content)
17+
class_node = find_first_class_node(result.value)
18+
return unless class_node
19+
20+
class_name = class_node.name
21+
method_names = get_all_method_names(class_node)
22+
23+
write_rbi_to_file(class_name, method_names)
24+
puts "Generated RBI file: #{@rbi_path}"
2425
end
2526

26-
# Takes the syntax tree and extracts the class name
27-
def extract_class_name(ast)
28-
class_node = ast[1].find { |node| node[0] == :class }
29-
class_node[1][1] if class_node
27+
private
28+
29+
def write_rbi_to_file(class_name, methods)
30+
rbi = +"class #{class_name}\n"
31+
32+
methods.each do |method|
33+
name = method[:name]
34+
params = method[:params]
35+
sig = if params.empty?
36+
" sig { void }"
37+
else
38+
typed_params = params.map { |p| "#{p}: T.untyped" }.join(", ")
39+
" sig { params(#{typed_params}).returns(T.untyped) }"
40+
end
41+
rbi << "#{sig}\n"
42+
def_line = " def #{name}"
43+
def_line << "(#{params.join(", ")})" unless params.empty?
44+
def_line << "; end\n\n"
45+
rbi << def_line
46+
end
47+
48+
rbi << "end\n"
49+
File.write(@rbi_path, rbi)
3050
end
3151

32-
def extract_method_names(ast)
33-
class_node = ast[1].find { |node| node[0] == :class }
34-
body = class_node[3] # :bodystmt
35-
return [] unless body && body[0].is_a?(Array)
52+
# Recursively find the first ClassNode in the AST
53+
def find_first_class_node(node)
54+
return node if node.is_a?(Prism::ClassNode)
55+
return nil unless node.respond_to?(:child_nodes)
56+
57+
node.child_nodes.each do |child|
58+
found = find_first_class_node(child)
59+
return found if found
60+
end
61+
end
62+
63+
# Goes through the class and grab every child node (they will be defined as defNode)
64+
def get_all_method_names(class_node)
65+
class_node.body.child_nodes
66+
.select { |child| child.is_a?(Prism::DefNode) }
67+
.map do |method_node|
68+
{
69+
name: method_node.name,
70+
params: get_method_params(method_node)
71+
}
72+
end
73+
end
74+
75+
def get_method_params(method_node)
76+
params_node = method_node.parameters
77+
return [] unless params_node
78+
79+
all_params = []
3680

37-
method_defs = body[0].select { |node| node[0] == :def }
38-
method_defs.map { |def_node| def_node[1][1] } # => ["hello", "goodbye"]
81+
all_params += params_node.requireds.map { |p| p.name }
82+
all_params += params_node.optionals.map { |p| p.name }
83+
all_params << params_node.rest.name if params_node.rest
84+
all_params += params_node.posts.map { |p| p.name }
85+
all_params += params_node.keywords.map { |p| p.name }
86+
all_params << params_node.keyword_rest.name if params_node.keyword_rest
87+
all_params << params_node.block.name if params_node.block
88+
all_params.compact
3989
end
4090
end
4191
end

test.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ class MyTest
22
def hello; end
33

44
def test(x); end
5+
6+
def add(x, y); end
57
end

test.rbi

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class MyTest
2+
sig { void }
3+
def hello; end
4+
5+
sig { params(x: T.untyped).returns(T.untyped) }
6+
def test(x); end
7+
8+
sig { params(x: T.untyped, y: T.untyped).returns(T.untyped) }
9+
def add(x, y); end
10+
end

0 commit comments

Comments
 (0)