diff --git a/lib/rbs/ast/declarations.rb b/lib/rbs/ast/declarations.rb index 7c43cef70..0a205a1a3 100644 --- a/lib/rbs/ast/declarations.rb +++ b/lib/rbs/ast/declarations.rb @@ -4,7 +4,15 @@ module Declarations class ModuleTypeParams attr_reader :params - TypeParam = Struct.new(:name, :variance, :skip_validation, keyword_init: true) + TypeParam = Struct.new(:name, :variance, :skip_validation, keyword_init: true) do + def to_json(*a) + { + name: name, + variance: variance, + skip_validation: skip_validation, + }.to_json(*a) + end + end def initialize() @params = [] diff --git a/rbs.gemspec b/rbs.gemspec index ae8db17ae..93365912e 100644 --- a/rbs.gemspec +++ b/rbs.gemspec @@ -43,4 +43,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency "rubocop-rubycw" spec.add_development_dependency "minitest-reporters", "~> 1.3.6" spec.add_development_dependency "json", "~> 2.3.0" + spec.add_development_dependency "json-schema", "~> 2.8" end diff --git a/schema/annotation.json b/schema/annotation.json new file mode 100644 index 000000000..415481608 --- /dev/null +++ b/schema/annotation.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Annotation associated to a declaration or a member: `%a{rbs:test}`, `%a{steep:deprecated}`, ...", + "type": "object", + "properties": { + "string": { + "type": "string" + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["string", "location"] +} diff --git a/schema/comment.json b/schema/comment.json new file mode 100644 index 000000000..e29b0fdc5 --- /dev/null +++ b/schema/comment.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "definitions": { + "comment": { + "type": "object", + "properties": { + "string": { + "type": "string" + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["string", "location"] + } + }, + "title": "Comment associated with a declaration or a member", + "oneOf": [ + { + "$ref": "#/definitions/comment" + }, + { + "type": "null" + } + ] +} diff --git a/schema/decls.json b/schema/decls.json new file mode 100644 index 000000000..456a79818 --- /dev/null +++ b/schema/decls.json @@ -0,0 +1,327 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "definitions": { + "alias": { + "title": "Type alias declaration: `type foo = Integer`, ...", + "type": "object", + "properties": { + "declaration": { + "type": "string", + "enum": ["alias"] + }, + "name": { + "type": "string" + }, + "type": { + "$ref": "types.json" + }, + "annotations": { + "type": "array", + "items": { + "$ref": "annotation.json" + } + }, + "location": { + "$ref": "location.json" + }, + "comment": { + "$ref": "comment.json" + } + }, + "required": ["declaration", "name", "type", "annotations", "location", "comment"] + }, + "constant": { + "title": "Constant declaration: `VERSION: String`, ...", + "type": "object", + "properties": { + "declaration": { + "type": "string", + "enum": ["constant"] + }, + "name": { + "type": "string" + }, + "type": { + "$ref": "types.json" + }, + "location": { + "$ref": "location.json" + }, + "comment": { + "$ref": "comment.json" + } + }, + "required": ["declaration", "name", "type", "comment", "location"] + }, + "global": { + "title": "Global declaration: `$DEBUG: bool`, ...", + "type": "object", + "properties": { + "declaration": { + "type": "string", + "enum": ["global"] + }, + "name": { + "type": "string" + }, + "type": { + "$ref": "types.json" + }, + "location": { + "$ref": "location.json" + }, + "comment": { + "$ref": "comment.json" + } + }, + "required": ["declaration", "name", "type", "comment", "location"] + }, + "moduleTypeParam": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "variance": { + "enum": ["covariant", "contravariant", "invariant"] + }, + "skip_validation": { + "type": "boolean" + } + }, + "required": ["name", "variance", "skip_validation"] + }, + "classMember": { + "oneOf": [ + { + "$ref": "members.json#/definitions/methodDefinition" + }, + { + "$ref": "members.json#/definitions/variable" + }, + { + "$ref": "members.json#/definitions/include" + }, + { + "$ref": "members.json#/definitions/extend" + }, + { + "$ref": "members.json#/definitions/prepend" + }, + { + "$ref": "members.json#/definitions/attribute" + }, + { + "$ref": "members.json#/definitions/visibility" + }, + { + "$ref": "members.json#/definitions/alias" + } + ] + }, + "class": { + "title": "Class declaration", + "type": "object", + "properties": { + "declaration": { + "enum": ["class"] + }, + "name": { + "type": "string" + }, + "type_params": { + "type": "object", + "properties": { + "params": { + "type": "array", + "items": { + "$ref": "#/definitions/moduleTypeParam" + } + } + }, + "required": ["params"] + }, + "members": { + "type": "array", + "items": { + "$ref": "#/definitions/classMember" + } + }, + "super_class": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "args": { + "type": "array", + "items": { + "$ref": "types.json" + } + } + }, + "required": ["name", "args"] + } + ] + }, + "annotations": { + "type": "array", + "items": { + "$ref": "annotation.json" + } + }, + "comment": { + "$ref": "comment.json" + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["declaration", "name", "type_params", "members", "super_class", "annotations", "comment", "location"] + }, + "module": { + "type": "object", + "properties": { + "declaration": { + "enum": ["module"] + }, + "name": { + "type": "string" + }, + "type_params": { + "type": "object", + "properties": { + "params": { + "type": "array", + "items": { + "$ref": "#/definitions/moduleTypeParam" + } + } + }, + "required": ["params"] + }, + "members": { + "type": "array", + "items": { + "$ref": "#/definitions/classMember" + } + }, + "self_type": { + "oneOf": [ + { + "$ref": "types.json" + }, + { + "type": "null" + } + ] + }, + "annotations": { + "type": "array", + "items": { + "$ref": "annotation.json" + } + }, + "comment": { + "$ref": "comment.json" + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["declaration", "name", "type_params", "members", "self_type", "annotations", "location", "comment"] + }, + "interfaceMember": { + "oneOf": [ + { + "allOf": [ + { + "$ref": "members.json#/definitions/methodDefinition" + }, + { + "type": "object", + "properties": { + "kind": { + "enum": ["instance"] + } + } + } + ] + }, + { + "$ref": "members.json#/definitions/include" + }, + { + "$ref": "members.json#/definitions/alias" + } + ] + }, + "interface": { + "type": "object", + "properties": { + "declaration": { + "enum": ["interface"] + }, + "name": { + "type": "string" + }, + "type_params": { + "type": "object", + "properties": { + "params": { + "type": "array", + "items": { + "$ref": "#/definitions/moduleTypeParam" + } + } + }, + "required": ["params"] + }, + "members": { + "type": "array", + "items": { + "$ref": "#/definitions/interfaceMember" + } + }, + "annotations": { + "type": "array", + "items": { + "$ref": "annotation.json" + } + }, + "comment": { + "$ref": "comment.json" + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["declaration", "name", "type_params", "members", "annotations", "comment", "location"] + } + }, + "oneOf": [ + { + "$ref": "#/definitions/alias" + }, + { + "$ref": "#/definitions/constant" + }, + { + "$ref": "#/definitions/global" + }, + { + "$ref": "#/definitions/class" + }, + { + "$ref": "#/definitions/module" + }, + { + "$ref": "#/definitions/interface" + } + ] +} diff --git a/schema/function.json b/schema/function.json new file mode 100644 index 000000000..00ad4910d --- /dev/null +++ b/schema/function.json @@ -0,0 +1,87 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "definitions": { + "param": { + "title": "Function parameter with type and optional name: `Integer size`, `::String name`, `untyped`, ...", + "type": "object", + "properties": { + "type": { + "$ref": "types.json" + }, + "name": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": ["type", "name"] + } + }, + "properties": { + "required_positionals": { + "title": "Required positional parameters", + "type": "array", + "items": { + "$ref": "#/definitions/param" + } + }, + "optional_positionals": { + "title": "Optional positional parameters", + "type": "array", + "items": { + "$ref": "#/definitions/param" + } + }, + "rest_positionals": { + "title": "Rest parameter", + "oneOf": [ + { + "$ref": "#/definitions/param" + }, + { + "type": "null" + } + ] + }, + "trailing_positionals": { + "title": "Trailing potisional parameters", + "type": "array", + "items": { + "$ref": "#/definitions/param" + } + }, + "required_keywords": { + "title": "Required keyword parameters", + "additionalProperties": { + "$ref": "#/definitions/param" + } + }, + "optional_keywords": { + "title": "Optional keyword parameters", + "additionalProperties": { + "$ref": "#/definitions/param" + } + }, + "rest_keywords": { + "title": "Rest keyword parameters", + "oneOf": [ + { + "$ref": "#/definitions/param" + }, + { + "type": "null" + } + ] + }, + "return_type": { + "title": "Return type of a function", + "$ref": "types.json" + } + }, + "required": ["required_positionals", "optional_positionals", "rest_positionals", "trailing_positionals", "required_keywords", "optional_keywords", "rest_keywords", "return_type"] +} diff --git a/schema/location.json b/schema/location.json new file mode 100644 index 000000000..ebbfca10f --- /dev/null +++ b/schema/location.json @@ -0,0 +1,56 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "definitions": { + "point": { + "type": "object", + "properties": { + "line": { + "type": "integer" + }, + "column": { + "type": "integer" + } + }, + "required": ["line", "column"] + }, + "buffer": { + "type": "object", + "properties": { + "name": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": ["name"] + }, + "location": { + "type": "object", + "properties": { + "start": { + "$ref": "#/definitions/point" + }, + "end": { + "$ref": "#/definitions/point" + }, + "buffer": { + "$ref": "#/definitions/buffer" + } + }, + "required": ["start", "end", "buffer"] + } + }, + "oneOf": [ + { + "$ref": "#/definitions/location" + }, + { + "type": "null" + } + ] +} diff --git a/schema/members.json b/schema/members.json new file mode 100644 index 000000000..1643eb2d0 --- /dev/null +++ b/schema/members.json @@ -0,0 +1,245 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "definitions": { + "methodDefinition": { + "type": "object", + "properties": { + "member": { + "type": "string", + "enum": ["method_definition"] + }, + "kind": { + "enum": ["instance", "singleton", "singleton_instance"] + }, + "types": { + "type": "array", + "items": { + "$ref": "methodType.json" + } + }, + "comment": { + "$ref": "comment.json" + }, + "annotations": { + "type": "array", + "items": { + "$ref": "annotation.json" + } + }, + "attributes": { + "type": "array", + "items": { + "enum": ["incompatible"] + } + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["member", "kind", "types", "comment", "annotations", "location"] + }, + "variable": { + "title": "Declaration for instance variables and class variables", + "description": "`@x: Integer`, `self.@x: String`, `@@name: Symbol`, ...", + "type": "object", + "properties": { + "member": { + "enum": ["instance_variable", "class_instance_variable", "class_variable"] + }, + "name": { + "type": "string" + }, + "type": { + "$ref": "types.json" + }, + "location": { + "$ref": "location.json" + }, + "comment": { + "$ref": "comment.json" + } + }, + "required": ["member", "name", "type", "location", "comment"] + }, + "include": { + "title": "Include mixin", + "properties": { + "member": { + "enum": ["include"] + }, + "name": { + "type": "string" + }, + "args": { + "type": "array", + "items": { + "$ref": "types.json" + } + }, + "annotations": { + "type": "array", + "items": { + "$ref": "annotation.json" + } + }, + "comment": { + "$ref": "comment.json" + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["member", "name", "args", "annotations", "comment", "location"] + }, + "extend": { + "title": "Extend mixin", + "properties": { + "member": { + "enum": ["extend"] + }, + "name": { + "type": "string" + }, + "args": { + "type": "array", + "items": { + "$ref": "types.json" + } + }, + "annotations": { + "type": "array", + "items": { + "$ref": "annotation.json" + } + }, + "comment": { + "$ref": "comment.json" + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["member", "name", "args", "annotations", "comment", "location"] + }, + "prepend": { + "title": "Prepend mixin", + "properties": { + "member": { + "enum": ["prepend"] + }, + "name": { + "type": "string" + }, + "args": { + "type": "array", + "items": { + "$ref": "types.json" + } + }, + "annotations": { + "type": "array", + "items": { + "$ref": "annotation.json" + } + }, + "comment": { + "$ref": "comment.json" + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["member", "name", "args", "annotations", "comment", "location"] + }, + "attribute": { + "title": "Attribute definitions", + "description": "`attr_reader`, `attr_accessor`, `attr_writer`", + "properties": { + "member": { + "type": "string", + "enum": ["attr_reader", "attr_accessor", "attr_writer"] + }, + "name": { + "type": "string" + }, + "type": { + "$ref": "types.json" + }, + "ivar_name": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + }, + { + "enum": [false] + } + ] + }, + "annotations": { + "type": "array", + "items": { + "$ref": "annotation.json" + } + }, + "comment": { + "$ref": "comment.json" + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["member", "name", "ivar_name", "type", "annotations", "comment", "location"] + }, + "visibility": { + "title": "Visibility specifier", + "description": "`public` and `private`.", + "type": "object", + "properties": { + "member": { + "type": "string", + "enum": ["public", "private"] + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["member", "location"] + }, + "alias": { + "title": "Alias declaration", + "description": "`alias to_s inspect`, `alias self.new self.allocate`, ...", + "properties": { + "member": { + "type": "string", + "enum": ["alias"] + }, + "new_name": { + "type": "string" + }, + "old_name": { + "type": "string" + }, + "kind": { + "type": "string", + "enum": ["instance", "singleton"] + }, + "annotations": { + "type": "array", + "items": { + "$ref": "annotation.json" + } + }, + "comment": { + "$ref": "comment.json" + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["member", "new_name", "old_name", "kind", "annotations", "comment", "location"] + } + } +} diff --git a/schema/methodType.json b/schema/methodType.json new file mode 100644 index 000000000..cc87ac8c4 --- /dev/null +++ b/schema/methodType.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "definitions": { + "block": { + "type": "object", + "properties": { + "type": { + "$ref": "function.json" + }, + "required": { + "type": "boolean" + } + }, + "required": ["type", "required"] + } + }, + "title": "Method type: `() -> void`, `[X] (::Integer) { (::String) -> X } -> Array[X]`, ...", + "type": "object", + "properties": { + "type_params": { + "type": "array", + "items": { + "type": "string" + } + }, + "type": { + "$ref": "function.json" + }, + "block": { + "oneOf": [ + { + "$ref": "#/definitions/block" + }, + { + "type": "null" + } + ] + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["type_params", "type", "block", "location"] +} diff --git a/schema/types.json b/schema/types.json new file mode 100644 index 000000000..a2387412e --- /dev/null +++ b/schema/types.json @@ -0,0 +1,299 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "definitions": { + "base": { + "title": "Base types", + "type": "object", + "properties": { + "class": { + "type": "string", + "enum": [ + "bool", + "void", + "untyped", + "nil", + "top", + "bot", + "self", + "instance", + "class" + ] + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["class", "location"] + }, + "variable": { + "title": "Type variables: `X`, `Y`, `Elem`, `Key`, ...", + "type": "object", + "properties": { + "class": { + "type": "string", + "enum": ["variable"] + }, + "name": { + "type": "string" + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["class", "name", "location"] + }, + "classSingleton": { + "title": "Class singleton type: `singleton(Object)`, `singleton(::Kernel)`, ...", + "type": "object", + "properties": { + "class": { + "type": "string", + "enum": ["class_singleton"] + }, + "name": { + "type": "string" + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["class", "name", "location"] + }, + "classInstance": { + "title": "Class instance type: `String`, `::Enumerable`, `Array[::Symbol]`, ...", + "type": "object", + "properties": { + "class": { + "type": "string", + "enum": ["class_instance"] + }, + "name": { + "type": "string" + }, + "args": { + "type": "array", + "items": { + "$ref": "#" + } + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["class", "name", "args", "location"] + }, + "interface": { + "title": "Interface type: `_Each[String, void]`, `_ToStr`, ...", + "type": "object", + "properties": { + "class": { + "type": "string", + "enum": ["interface"] + }, + "name": { + "type": "string" + }, + "args": { + "type": "array", + "items": { + "$ref": "#" + } + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["class", "name", "args", "location"] + }, + "alias": { + "title": "Type alias: `u`, `ty`, `json`, ...", + "type": "object", + "properties": { + "class": { + "type": "string", + "enum": ["alias"] + }, + "name": { + "type": "string" + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["class", "name", "location"] + }, + "tuple": { + "title": "Tuple type: `[Foo, bar]`, ...", + "type": "object", + "properties": { + "class": { + "type": "string", + "enum": ["tuple"] + }, + "types": { + "type": "array", + "items": { + "$ref": "#" + } + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["class", "types", "location"] + }, + "record": { + "title": "Record type: `{ id: Integer, email: String }`, ...", + "type": "object", + "properties": { + "class": { + "type": "string", + "enum": ["record"] + }, + "fields": { + "type": "object", + "additionalProperties": { + "$ref": "#" + } + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["class", "fields", "location"] + }, + "optional": { + "title": "Optional types: `Integer?`, ...", + "type": "object", + "properties": { + "class": { + "type": "string", + "enum": ["optional"] + }, + "type": { + "$ref": "#" + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["class", "type", "location"] + }, + "union": { + "title": "Union types: `Integer | String`, ...", + "type": "object", + "properties": { + "class": { + "type": "string", + "enum": ["union"] + }, + "types": { + "type": "array", + "items": { + "$ref": "#" + } + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["class", "types", "location"] + }, + "intersection": { + "title": "Intersection types: `Integer & String`, ...", + "type": "object", + "properties": { + "class": { + "type": "string", + "enum": ["intersection"] + }, + "types": { + "type": "array", + "items": { + "$ref": "#" + } + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["class", "types", "location"] + }, + "proc": { + "title": "Proc types: `^() -> void`, ...", + "type": "object", + "properties": { + "class": { + "type": "string", + "enum": ["proc"] + }, + "type": { + "$ref": "function.json" + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["class", "type", "location"] + }, + "literal": { + "title": "Literal types: `1`, `:foo`, `\"foo\"`, ...", + "type": "object", + "properties": { + "class": { + "type": "string", + "enum": ["literal"] + }, + "literal": { + "type": "string" + }, + "location": { + "$ref": "location.json" + } + }, + "required": ["class", "literal", "location"] + } + }, + "title": "Type", + "oneOf": [ + { + "$ref": "#/definitions/base" + }, + { + "$ref": "#/definitions/variable" + }, + { + "$ref": "#/definitions/classInstance" + }, + { + "$ref": "#/definitions/classSingleton" + }, + { + "$ref": "#/definitions/interface" + }, + { + "$ref": "#/definitions/alias" + }, + { + "$ref": "#/definitions/tuple" + }, + { + "$ref": "#/definitions/record" + }, + { + "$ref": "#/definitions/union" + }, + { + "$ref": "#/definitions/intersection" + }, + { + "$ref": "#/definitions/optional" + }, + { + "$ref": "#/definitions/proc" + }, + { + "$ref": "#/definitions/literal" + } + ] +} diff --git a/test/json_validator.rb b/test/json_validator.rb new file mode 100644 index 000000000..f7b272c2b --- /dev/null +++ b/test/json_validator.rb @@ -0,0 +1,53 @@ +require "json-schema" + +class JSONValidator + class Validator + attr_reader :file + + def initialize(name) + @file = Pathname(__dir__) + "../schema/#{name}.json" + end + + def validate(object, fragment: nil) + JSON::Validator.validate( + file.to_s, + object, + { fragment: fragment } + ) + end + + def validate!(object, fragment: nil) + JSON::Validator.validate!( + file.to_s, + object, + { fragment: fragment } + ) + end + end + + class < void"), "proc" + refute_type parse_type("string?"), "proc" + + assert_type parse_type("30"), "literal" + assert_type parse_type(":foo"), "literal" + end + + def test_method_type_schema + JSONValidator.method_type.validate!( + parse_method_type("[G] (A a, ?B, *C, d: D, ?e: E e, **f) ?{ (G) -> void } -> String").to_json + ) + end + + def test_decls + assert_decl RBS::Parser.parse_signature("type Steep::foo = untyped")[0], :alias + + assert_decl RBS::Parser.parse_signature('Steep::VERSION: "1.2.3"')[0], :constant + + assert_decl RBS::Parser.parse_signature('$SIZE: Integer?')[0], :global + end + + def assert_member(member, name=nil) + if name + JSONValidator.members.validate!( + member.to_json, + fragment: "#/definitions/#{name}" + ) + end + + JSONValidator.members.validate!(member.to_json) + end + + def test_members + members = RBS::Parser.parse_signature(< Integer + + @foo: Integer + self.@bar: String + @@baz: Symbol + + include Foo + extend _Baz + prepend Bar[Integer, String] + + attr_reader name: String + attr_accessor age (@age): Integer + attr_writer email(): String? + + private + public + + alias foo bar + alias self.foo self.bar +end +EOF + + assert_member members[0], :methodDefinition + + assert_member members[1], :variable + assert_member members[2], :variable + assert_member members[3], :variable + + assert_member members[4], :include + assert_member members[5], :extend + assert_member members[6], :prepend + + assert_member members[7], :attribute + assert_member members[8], :attribute + assert_member members[9], :attribute + + assert_member members[10], :visibility + assert_member members[11], :visibility + + assert_member members[12], :alias + assert_member members[13], :alias + end + + def test_class_decl + decl, = RBS::Parser.parse_signature(< Integer + + @foo: Integer + self.@bar: String + @@baz: Symbol + + include Foo + extend _Baz + prepend Bar[Integer, String] + + attr_reader name: String + attr_accessor age (@age): Integer + attr_writer email(): String? + + private + public + + alias foo bar + alias self.foo self.bar +end +EOF + + assert_decl decl, :class + end + + def test_module_decl + decl, = RBS::Parser.parse_signature(< Integer + + @foo: Integer + self.@bar: String + @@baz: Symbol + + include Foo + extend _Baz + prepend Bar[Integer, String] + + attr_reader name: String + attr_accessor age (@age): Integer + attr_writer email(): String? + + private + public + + alias foo bar + alias self.foo self.bar +end +EOF + + assert_decl decl, :module + end + + def test_interface_decl + decl, = RBS::Parser.parse_signature(< Integer + + include _Foo +end +EOF + + assert_decl decl, :interface + end +end