Skip to content

Commit ae222b0

Browse files
committed
Implement parser
1 parent c1b1cc5 commit ae222b0

File tree

3 files changed

+224
-2
lines changed

3 files changed

+224
-2
lines changed

lib/rbs/inline_parser.rb

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def initialize(location, message)
2626
NotImplementedYet = _ = Class.new(Base)
2727
NonConstantClassName = _ = Class.new(Base)
2828
NonConstantModuleName = _ = Class.new(Base)
29+
NonConstantSuperClassName = _ = Class.new(Base)
2930
TopLevelMethodDefinition = _ = Class.new(Base)
3031
TopLevelAttributeDefinition = _ = Class.new(Base)
3132
UnusedInlineAnnotation = _ = Class.new(Base)
@@ -100,7 +101,13 @@ def visit_class_node(node)
100101
return
101102
end
102103

103-
class_decl = AST::Ruby::Declarations::ClassDecl.new(buffer, class_name, node, nil)
104+
# Parse super class if present
105+
super_class = if node.superclass
106+
node.inheritance_operator_loc or raise
107+
parse_super_class(node.superclass, node.inheritance_operator_loc)
108+
end
109+
110+
class_decl = AST::Ruby::Declarations::ClassDecl.new(buffer, class_name, node, super_class)
104111
insert_declaration(class_decl)
105112
push_module_nesting(class_decl) do
106113
visit_child_nodes(node)
@@ -352,6 +359,39 @@ def report_unused_block(block)
352359
end
353360
end
354361
end
362+
363+
def parse_super_class(super_class_expr, inheritance_operator_loc)
364+
# Check if the superclass is a constant
365+
unless super_class_name = constant_as_type_name(super_class_expr)
366+
diagnostics << Diagnostic::NonConstantSuperClassName.new(
367+
rbs_location(super_class_expr.location),
368+
"Super class name must be a constant"
369+
)
370+
return nil
371+
end
372+
373+
# Look for type application annotation in trailing comments
374+
# For example: class StringArray < Array #[String]
375+
trailing_block = comments.trailing_block!(super_class_expr.location)
376+
type_annotation = nil
377+
378+
if trailing_block
379+
case annotation = trailing_block.trailing_annotation([])
380+
when AST::Ruby::Annotations::TypeApplicationAnnotation
381+
type_annotation = annotation
382+
else
383+
report_unused_annotation(annotation)
384+
end
385+
end
386+
387+
# Create SuperClass object
388+
AST::Ruby::Declarations::ClassDecl::SuperClass.new(
389+
rbs_location(super_class_expr.location),
390+
rbs_location(inheritance_operator_loc),
391+
super_class_name,
392+
type_annotation
393+
)
394+
end
355395
end
356396
end
357397
end

sig/inline_parser.rbs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ module RBS
2929
class NonConstantModuleName < Base
3030
end
3131

32+
class NonConstantSuperClassName < Base
33+
end
34+
3235
class TopLevelMethodDefinition < Base
3336
end
3437

@@ -51,7 +54,7 @@ module RBS
5154
end
5255

5356
type t = NotImplementedYet
54-
| NonConstantClassName | NonConstantModuleName
57+
| NonConstantClassName | NonConstantModuleName | NonConstantSuperClassName
5558
| TopLevelMethodDefinition | TopLevelAttributeDefinition
5659
| UnusedInlineAnnotation | AnnotationSyntaxError
5760
| MixinMultipleArguments | MixinNonConstantModule
@@ -102,6 +105,8 @@ module RBS
102105
def parse_mixin_call: (Prism::CallNode) -> void
103106

104107
def parse_attribute_call: (Prism::CallNode) -> void
108+
109+
def parse_super_class: (Prism::node, Prism::Location) -> Declarations::ClassDecl::SuperClass?
105110
end
106111
end
107112
end

test/rbs/inline_parser_test.rb

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,4 +943,181 @@ class Foo
943943
end
944944
end
945945
end
946+
947+
def test_parse__class_with_super_class
948+
result = parse(<<~RUBY)
949+
class Child < Parent
950+
end
951+
RUBY
952+
953+
assert_empty result.diagnostics
954+
955+
result.declarations[0].tap do |decl|
956+
assert_instance_of RBS::AST::Ruby::Declarations::ClassDecl, decl
957+
assert_equal RBS::TypeName.parse("Child"), decl.class_name
958+
assert_instance_of RBS::AST::Ruby::Declarations::ClassDecl::SuperClass, decl.super_class
959+
assert_equal RBS::TypeName.parse("Parent"), decl.super_class.name
960+
assert_empty decl.super_class.args
961+
end
962+
end
963+
964+
def test_parse__class_with_super_class_nested
965+
result = parse(<<~RUBY)
966+
class UsersController < ApplicationController
967+
class Error < StandardError
968+
end
969+
end
970+
RUBY
971+
972+
assert_empty result.diagnostics
973+
974+
result.declarations[0].tap do |decl|
975+
assert_instance_of RBS::AST::Ruby::Declarations::ClassDecl, decl
976+
assert_equal RBS::TypeName.parse("UsersController"), decl.class_name
977+
assert_instance_of RBS::AST::Ruby::Declarations::ClassDecl::SuperClass, decl.super_class
978+
assert_equal RBS::TypeName.parse("ApplicationController"), decl.super_class.name
979+
assert_empty decl.super_class.args
980+
981+
decl.members[0].tap do |member|
982+
assert_instance_of RBS::AST::Ruby::Declarations::ClassDecl, member
983+
assert_equal RBS::TypeName.parse("Error"), member.class_name
984+
assert_instance_of RBS::AST::Ruby::Declarations::ClassDecl::SuperClass, member.super_class
985+
assert_equal RBS::TypeName.parse("StandardError"), member.super_class.name
986+
assert_empty member.super_class.args
987+
end
988+
end
989+
end
990+
991+
def test_parse__class_with_super_class_type_application
992+
result = parse(<<~RUBY)
993+
class StringArray < Array #[String]
994+
end
995+
RUBY
996+
997+
assert_empty result.diagnostics
998+
999+
result.declarations[0].tap do |decl|
1000+
assert_instance_of RBS::AST::Ruby::Declarations::ClassDecl, decl
1001+
assert_equal RBS::TypeName.parse("StringArray"), decl.class_name
1002+
assert_instance_of RBS::AST::Ruby::Declarations::ClassDecl::SuperClass, decl.super_class
1003+
assert_equal RBS::TypeName.parse("Array"), decl.super_class.name
1004+
assert_equal 1, decl.super_class.args.size
1005+
assert_equal "String", decl.super_class.args[0].to_s
1006+
end
1007+
end
1008+
1009+
def test_parse__class_with_super_class_complex_type_application
1010+
result = parse(<<~RUBY)
1011+
class MyHash < Hash #[Symbol, Array[Integer]]
1012+
end
1013+
RUBY
1014+
1015+
assert_empty result.diagnostics
1016+
1017+
result.declarations[0].tap do |decl|
1018+
assert_instance_of RBS::AST::Ruby::Declarations::ClassDecl, decl
1019+
assert_equal RBS::TypeName.parse("MyHash"), decl.class_name
1020+
assert_instance_of RBS::AST::Ruby::Declarations::ClassDecl::SuperClass, decl.super_class
1021+
assert_equal RBS::TypeName.parse("Hash"), decl.super_class.name
1022+
assert_equal 2, decl.super_class.args.size
1023+
assert_equal "Symbol", decl.super_class.args[0].to_s
1024+
assert_equal "Array[Integer]", decl.super_class.args[1].to_s
1025+
end
1026+
end
1027+
1028+
def test_parse__class_with_qualified_super_class
1029+
result = parse(<<~RUBY)
1030+
class MyError < ActiveRecord::RecordNotFound
1031+
end
1032+
RUBY
1033+
1034+
assert_empty result.diagnostics
1035+
1036+
result.declarations[0].tap do |decl|
1037+
assert_instance_of RBS::AST::Ruby::Declarations::ClassDecl, decl
1038+
assert_equal RBS::TypeName.parse("MyError"), decl.class_name
1039+
assert_instance_of RBS::AST::Ruby::Declarations::ClassDecl::SuperClass, decl.super_class
1040+
assert_equal RBS::TypeName.parse("ActiveRecord::RecordNotFound"), decl.super_class.name
1041+
assert_empty decl.super_class.args
1042+
end
1043+
end
1044+
1045+
def test_error__class_with_non_constant_super_class
1046+
result = parse(<<~RUBY)
1047+
class Child < parent
1048+
end
1049+
RUBY
1050+
1051+
assert_equal 1, result.diagnostics.size
1052+
assert_any!(result.diagnostics) do |diagnostic|
1053+
assert_instance_of RBS::InlineParser::Diagnostic::NonConstantSuperClassName, diagnostic
1054+
assert_equal "parent", diagnostic.location.source
1055+
assert_equal "Super class name must be a constant", diagnostic.message
1056+
end
1057+
1058+
# The class should still be created but without super class
1059+
result.declarations[0].tap do |decl|
1060+
assert_instance_of RBS::AST::Ruby::Declarations::ClassDecl, decl
1061+
assert_equal RBS::TypeName.parse("Child"), decl.class_name
1062+
assert_nil decl.super_class
1063+
end
1064+
end
1065+
1066+
def test_error__class_with_dynamic_super_class
1067+
result = parse(<<~RUBY)
1068+
Parent = Class.new
1069+
class Child < Parent.new
1070+
end
1071+
RUBY
1072+
1073+
assert_equal 1, result.diagnostics.size
1074+
assert_any!(result.diagnostics) do |diagnostic|
1075+
assert_instance_of RBS::InlineParser::Diagnostic::NonConstantSuperClassName, diagnostic
1076+
assert_equal "Parent.new", diagnostic.location.source
1077+
assert_equal "Super class name must be a constant", diagnostic.message
1078+
end
1079+
1080+
# The class should still be created but without super class
1081+
result.declarations[0].tap do |decl|
1082+
assert_instance_of RBS::AST::Ruby::Declarations::ClassDecl, decl
1083+
assert_equal RBS::TypeName.parse("Child"), decl.class_name
1084+
assert_nil decl.super_class
1085+
end
1086+
end
1087+
1088+
def test_parse__class_with_super_class_and_members
1089+
result = parse(<<~RUBY)
1090+
class Person < ActiveRecord::Base #[Person]
1091+
attr_reader :name #: String
1092+
1093+
def age
1094+
25
1095+
end
1096+
end
1097+
RUBY
1098+
1099+
assert_empty result.diagnostics
1100+
1101+
result.declarations[0].tap do |decl|
1102+
assert_instance_of RBS::AST::Ruby::Declarations::ClassDecl, decl
1103+
assert_equal RBS::TypeName.parse("Person"), decl.class_name
1104+
assert_instance_of RBS::AST::Ruby::Declarations::ClassDecl::SuperClass, decl.super_class
1105+
assert_equal RBS::TypeName.parse("ActiveRecord::Base"), decl.super_class.name
1106+
assert_equal 1, decl.super_class.args.size
1107+
assert_equal "Person", decl.super_class.args[0].to_s
1108+
1109+
assert_equal 2, decl.members.size
1110+
1111+
decl.members[0].tap do |member|
1112+
assert_instance_of RBS::AST::Ruby::Members::AttrReaderMember, member
1113+
assert_equal [:name], member.names
1114+
assert_equal "String", member.type.to_s
1115+
end
1116+
1117+
decl.members[1].tap do |member|
1118+
assert_instance_of RBS::AST::Ruby::Members::DefMember, member
1119+
assert_equal :age, member.name
1120+
end
1121+
end
1122+
end
9461123
end

0 commit comments

Comments
 (0)