diff --git a/docs/inline.md b/docs/inline.md index 731dac35a6..5dc271f744 100644 --- a/docs/inline.md +++ b/docs/inline.md @@ -349,9 +349,35 @@ class Calculator end ``` +### Method visibility + +Inline RBS detects `public` and `private` calls, and applies the visibility to the methods, like RBS. + +```ruby +class Calculator + def add(x, y) = x + y # public + + private + + def helper = nil # private + + public + + def sub(x, y) = x - y # public + + private def secret = nil # private +end +``` + +- `private`/`public` without arguments switches the visibility of the instance methods defined after the call +- `private def ...`/`public def ...` applies the visibility to that method definition + +Note that the visibility is applied only when the method is called without receiver, and the symbol arguments, like `private :foo, :bar`, are ignored. + ### Current Limitations -- Method visibility declaration is not supported yet +- `private :foo, :bar` style visibility declaration is not supported +- `protected`, `private_class_method`, and `module_function` are not supported - The `class << self` syntax is not supported - Top-level method definitions (outside any class/module) are not supported - Method definitions with a non-self receiver (e.g. `def obj.foo`) are not supported @@ -429,9 +455,25 @@ end The attribute definitions are ignored because the names are given by string literals and local variables. +### Attribute visibility + +The attributes respect the visibility declaration, the same as [method definitions](#method-visibility). + +```ruby +class Person + attr_reader :name #: String + + private + + attr_accessor :password #: String + + private attr_writer :token #: String +end +``` + ### Current Limitations -- Attribute visibility is not supported yet. All attributes are _public_ +- `private :foo, :bar` style visibility declaration is not supported - Top-level attribute definitions (outside any class/module) are not supported ## Mixin diff --git a/lib/rbs/ast/ruby/members.rb b/lib/rbs/ast/ruby/members.rb index c80dcc2611..41513bb328 100644 --- a/lib/rbs/ast/ruby/members.rb +++ b/lib/rbs/ast/ruby/members.rb @@ -552,14 +552,16 @@ class DefMember < Base attr_reader :kind attr_reader :method_type attr_reader :leading_comment + attr_reader :visibility - def initialize(buffer, name, node, method_type, leading_comment, kind: :instance) + def initialize(buffer, name, node, method_type, leading_comment, kind: :instance, visibility: nil) super(buffer) @name = name @node = node @kind = kind @method_type = method_type @leading_comment = leading_comment + @visibility = visibility end def singleton? @@ -596,7 +598,8 @@ def type_fingerprint kind.to_s, name.to_s, method_type.type_fingerprint, - leading_comment&.as_comment&.string + leading_comment&.as_comment&.string, + visibility&.to_s ] end end @@ -652,13 +655,15 @@ class AttributeMember < Base attr_reader :name_nodes attr_reader :type_annotation attr_reader :leading_comment + attr_reader :visibility - def initialize(buffer, node, name_nodes, leading_comment, type_annotation) + def initialize(buffer, node, name_nodes, leading_comment, type_annotation, visibility: nil) super(buffer) @node = node @name_nodes = name_nodes @leading_comment = leading_comment @type_annotation = type_annotation + @visibility = visibility end def names @@ -687,7 +692,8 @@ def type_fingerprint self.class.name, names.map(&:to_s), type_annotation&.type_fingerprint, - leading_comment&.as_comment&.string + leading_comment&.as_comment&.string, + visibility&.to_s ] end end @@ -701,6 +707,38 @@ class AttrWriterMember < AttributeMember class AttrAccessorMember < AttributeMember end + class VisibilityMember < Base + attr_reader :node + + def initialize(buffer, node) + super(buffer) + @node = node + end + + def location + rbs_location(node.location) + end + + def type_fingerprint + [ + "members/visibility", + self.class.name + ] + end + end + + class PublicMember < VisibilityMember + def visibility + :public + end + end + + class PrivateMember < VisibilityMember + def visibility + :private + end + end + class InstanceVariableMember < Base attr_reader :annotation diff --git a/lib/rbs/definition_builder.rb b/lib/rbs/definition_builder.rb index ab0f6067de..88f53842eb 100644 --- a/lib/rbs/definition_builder.rb +++ b/lib/rbs/definition_builder.rb @@ -891,7 +891,7 @@ def define_method(methods, definition, method, subst, self_type_methods, defined method_definition = Definition::Method.new( super_method: existing_method, defs: existing_method.defs.map { |defn| defn.update(implemented_in: implemented_in) }, - accessibility: :public, + accessibility: method.accessibility, alias_of: existing_method.alias_of, alias_member: nil ) @@ -913,7 +913,7 @@ def define_method(methods, definition, method, subst, self_type_methods, defined method_definition = Definition::Method.new( super_method: existing_method, defs: defs, - accessibility: :public, + accessibility: method.accessibility, alias_of: nil, alias_member: nil ) diff --git a/lib/rbs/definition_builder/method_builder.rb b/lib/rbs/definition_builder/method_builder.rb index 2dc3a706bf..49167933e0 100644 --- a/lib/rbs/definition_builder/method_builder.rb +++ b/lib/rbs/definition_builder/method_builder.rb @@ -114,7 +114,7 @@ def build_instance(type_name) subst = entry.align_params(decl) case decl when AST::Declarations::Base - each_rbs_member_with_accessibility(decl.members) do |member, accessibility| + each_member_with_accessibility(decl.members) do |member, accessibility| case member when AST::Members::MethodDefinition case member.kind @@ -147,7 +147,7 @@ def build_instance(type_name) end end when AST::Ruby::Declarations::Base - decl.members.each do |member| + each_member_with_accessibility(decl.members) do |member, accessibility| case member when AST::Ruby::Members::DefMember if member.instance? @@ -155,11 +155,11 @@ def build_instance(type_name) methods, type, member: member, - accessibility: :public + accessibility: member.visibility || accessibility ) end when AST::Ruby::Members::AttrReaderMember, AST::Ruby::Members::AttrWriterMember, AST::Ruby::Members::AttrAccessorMember - build_ruby_attribute(methods, type, member: member, accessibility: :public) + build_ruby_attribute(methods, type, member: member, accessibility: member.visibility || accessibility) end end end @@ -192,7 +192,7 @@ def build_singleton(type_name) end when AST::Ruby::Members::DefMember if member.singleton? - build_method(methods, type, member: member, accessibility: :public) + build_method(methods, type, member: member, accessibility: member.visibility || :public) end end end @@ -271,12 +271,12 @@ def build_method(methods, type, member:, accessibility:) end end - def each_rbs_member_with_accessibility(members, accessibility: :public) + def each_member_with_accessibility(members, accessibility: :public) members.each do |member| case member - when AST::Members::Public + when AST::Members::Public, AST::Ruby::Members::PublicMember accessibility = :public - when AST::Members::Private + when AST::Members::Private, AST::Ruby::Members::PrivateMember accessibility = :private else yield member, accessibility diff --git a/lib/rbs/environment.rb b/lib/rbs/environment.rb index 0fb4347142..4889206d65 100644 --- a/lib/rbs/environment.rb +++ b/lib/rbs/environment.rb @@ -796,7 +796,8 @@ def resolve_ruby_member(resolver, member, context:) member.node, member.method_type.map_type_name {|name, _, _| absolute_type_name(resolver, nil, name, context: context) }, member.leading_comment, - kind: member.kind + kind: member.kind, + visibility: member.visibility ) when AST::Ruby::Members::IncludeMember resolved_annotation = member.annotation&.map_type_name {|name, _, _| absolute_type_name(resolver, nil, name, context: context) } @@ -829,7 +830,8 @@ def resolve_ruby_member(resolver, member, context:) member.node, member.name_nodes, member.leading_comment, - resolved_type_annotation + resolved_type_annotation, + visibility: member.visibility ) when AST::Ruby::Members::AttrWriterMember resolved_type_annotation = member.type_annotation&.map_type_name {|name, _, _| absolute_type_name(resolver, nil, name, context: context) } @@ -838,7 +840,8 @@ def resolve_ruby_member(resolver, member, context:) member.node, member.name_nodes, member.leading_comment, - resolved_type_annotation + resolved_type_annotation, + visibility: member.visibility ) when AST::Ruby::Members::AttrAccessorMember resolved_type_annotation = member.type_annotation&.map_type_name {|name, _, _| absolute_type_name(resolver, nil, name, context: context) } @@ -847,8 +850,11 @@ def resolve_ruby_member(resolver, member, context:) member.node, member.name_nodes, member.leading_comment, - resolved_type_annotation + resolved_type_annotation, + visibility: member.visibility ) + when AST::Ruby::Members::PublicMember, AST::Ruby::Members::PrivateMember + member when AST::Ruby::Members::InstanceVariableMember resolved_annotation = member.annotation.map_type_name {|name| absolute_type_name(resolver, nil, name, context: context) } AST::Ruby::Members::InstanceVariableMember.new( diff --git a/lib/rbs/inline_parser.rb b/lib/rbs/inline_parser.rb index ed38f22d9f..f131c79a6c 100644 --- a/lib/rbs/inline_parser.rb +++ b/lib/rbs/inline_parser.rb @@ -55,7 +55,7 @@ def self.parse(buffer, prism) end class Parser < Prism::Visitor - attr_reader :module_nesting, :result, :comments + attr_reader :module_nesting, :result, :comments, :current_visibility include AST::Ruby::Helpers::ConstantHelper include AST::Ruby::Helpers::LocationHelper @@ -64,6 +64,7 @@ def initialize(result) @result = result @module_nesting = [] @comments = CommentAssociation.build(result.buffer, result.prism_result) + @current_visibility = nil end def buffer @@ -89,6 +90,14 @@ def push_module_nesting(mod) module_nesting.pop() end + def push_visibility(visibility) + old_visibility = current_visibility + @current_visibility = visibility + yield + ensure + @current_visibility = old_visibility + end + def skip_node?(node) if ref = comments.leading_block(node) if ref.block.each_paragraph([]).any? { _1.is_a?(AST::Ruby::Annotations::SkipAnnotation) } @@ -225,7 +234,7 @@ def visit_def_node(node) method_type, leading_unuseds, trailing_unused = AST::Ruby::Members::MethodTypeAnnotation.build(leading_block, trailing_block, [], node) report_unused_annotation(trailing_unused, *leading_unuseds) - defn = AST::Ruby::Members::DefMember.new(buffer, node.name, node, method_type, leading_block, kind: kind) + defn = AST::Ruby::Members::DefMember.new(buffer, node.name, node, method_type, leading_block, kind: kind, visibility: current_visibility) current.members << defn # Skip other comments in `def` node @@ -264,6 +273,13 @@ def visit_call_node(node) "Top-level attribute definition is not supported" ) end + when :public, :private + case current = current_module + when AST::Ruby::Declarations::ClassDecl, AST::Ruby::Declarations::ModuleDecl + parse_visibility_call(node) + else + visit_child_nodes(node) + end else visit_child_nodes(node) end @@ -386,11 +402,11 @@ def parse_attribute_call(node) # Create the appropriate member type member = case node.name when :attr_reader - AST::Ruby::Members::AttrReaderMember.new(buffer, node, name_nodes, leading_block, type_annotation) + AST::Ruby::Members::AttrReaderMember.new(buffer, node, name_nodes, leading_block, type_annotation, visibility: current_visibility) when :attr_writer - AST::Ruby::Members::AttrWriterMember.new(buffer, node, name_nodes, leading_block, type_annotation) + AST::Ruby::Members::AttrWriterMember.new(buffer, node, name_nodes, leading_block, type_annotation, visibility: current_visibility) when :attr_accessor - AST::Ruby::Members::AttrAccessorMember.new(buffer, node, name_nodes, leading_block, type_annotation) + AST::Ruby::Members::AttrAccessorMember.new(buffer, node, name_nodes, leading_block, type_annotation, visibility: current_visibility) else raise "Unexpected attribute method: #{node.name}" end @@ -398,6 +414,30 @@ def parse_attribute_call(node) current_module!.members << member end + def parse_visibility_call(node) + if node.arguments && !node.arguments.arguments.empty? + # `private def foo; end`, `private attr_reader :foo` + visibility = node.name == :public ? :public : :private #: AST::Members::visibility + push_visibility(visibility) do + visit_child_nodes(node) + end + else + return if skip_node?(node) + + member = + case node.name + when :public + AST::Ruby::Members::PublicMember.new(buffer, node) + when :private + AST::Ruby::Members::PrivateMember.new(buffer, node) + else + raise "Unexpected visibility method: #{node.name}" + end + + current_module!.members << member + end + end + def parse_constant_declaration(node) # Create TypeName for the constant unless constant_name = constant_as_type_name(node) diff --git a/sig/ast/ruby/members.rbs b/sig/ast/ruby/members.rbs index 253e117c7c..8e69f7acd5 100644 --- a/sig/ast/ruby/members.rbs +++ b/sig/ast/ruby/members.rbs @@ -17,6 +17,7 @@ module RBS | AttrReaderMember | AttrWriterMember | AttrAccessorMember | InstanceVariableMember | ModuleSelfMember + | PublicMember | PrivateMember class MethodTypeAnnotation class DocStyle @@ -87,8 +88,9 @@ module RBS attr_reader kind: :instance | :singleton attr_reader method_type: MethodTypeAnnotation attr_reader leading_comment: CommentBlock? + attr_reader visibility: AST::Members::visibility? - def initialize: (Buffer, Symbol name, Prism::DefNode node, MethodTypeAnnotation, CommentBlock? leading_comment, ?kind: :instance | :singleton) -> void + def initialize: (Buffer, Symbol name, Prism::DefNode node, MethodTypeAnnotation, CommentBlock? leading_comment, ?kind: :instance | :singleton, ?visibility: AST::Members::visibility?) -> void def singleton?: () -> bool @@ -143,7 +145,9 @@ module RBS attr_reader leading_comment: CommentBlock? - def initialize: (Buffer, Prism::CallNode, Array[Prism::SymbolNode] name_nodes, RBS::AST::Ruby::CommentBlock? leading_comment, Annotations::NodeTypeAssertion? type_annotation) -> void + attr_reader visibility: AST::Members::visibility? + + def initialize: (Buffer, Prism::CallNode, Array[Prism::SymbolNode] name_nodes, RBS::AST::Ruby::CommentBlock? leading_comment, Annotations::NodeTypeAssertion? type_annotation, ?visibility: AST::Members::visibility?) -> void def names: () -> Array[Symbol] @@ -165,6 +169,24 @@ module RBS class AttrAccessorMember < AttributeMember end + class VisibilityMember < Base + attr_reader node: Prism::CallNode + + def initialize: (Buffer, Prism::CallNode) -> void + + def location: () -> Location + + def type_fingerprint: () -> untyped + end + + class PublicMember < VisibilityMember + def visibility: () -> :public + end + + class PrivateMember < VisibilityMember + def visibility: () -> :private + end + class InstanceVariableMember < Base attr_reader annotation: Annotations::InstanceVariableAnnotation diff --git a/sig/inline_parser.rbs b/sig/inline_parser.rbs index 5811e9a4d7..6fb2bf6fff 100644 --- a/sig/inline_parser.rbs +++ b/sig/inline_parser.rbs @@ -86,6 +86,8 @@ module RBS attr_reader module_nesting: Array[module_context] + attr_reader current_visibility: AST::Members::visibility? + def initialize: (Result) -> void def buffer: () -> Buffer @@ -98,6 +100,8 @@ module RBS def push_module_nesting: [T] (module_context) { () -> T } -> T + def push_visibility: [T] (AST::Members::visibility) { () -> T } -> T + # Returns `true` if the node is a comment block including `@rbs skip` annotation # # Doesn't update the `association` flag if returning `false`. @@ -118,6 +122,8 @@ module RBS def parse_attribute_call: (Prism::CallNode) -> void + def parse_visibility_call: (Prism::CallNode) -> void + def parse_constant_declaration: (Prism::ConstantWriteNode | Prism::ConstantPathWriteNode) -> void def parse_super_class: (Prism::node, Prism::Location) -> Declarations::ClassDecl::SuperClass? diff --git a/sig/method_builder.rbs b/sig/method_builder.rbs index e9bef08594..f37fb6c142 100644 --- a/sig/method_builder.rbs +++ b/sig/method_builder.rbs @@ -78,7 +78,8 @@ module RBS def build_method: (Methods, Methods::instance_type, member: AST::Members::MethodDefinition | AST::Ruby::Members::DefMember, accessibility: Definition::accessibility) -> void - def each_rbs_member_with_accessibility: (Array[AST::Members::t | AST::Declarations::t], ?accessibility: Definition::accessibility) { (AST::Members::t | AST::Declarations::t, Definition::accessibility) -> void } -> void + def each_member_with_accessibility: (Array[AST::Members::t | AST::Declarations::t], ?accessibility: Definition::accessibility) { (AST::Members::t | AST::Declarations::t, Definition::accessibility) -> void } -> void + | (Array[AST::Ruby::Declarations::ClassDecl::member], ?accessibility: Definition::accessibility) { (AST::Ruby::Declarations::ClassDecl::member, Definition::accessibility) -> void } -> void def update: (env: Environment, except: _Each[TypeName]) -> MethodBuilder end diff --git a/test/rbs/definition_builder_test.rb b/test/rbs/definition_builder_test.rb index b82a2d020d..4a6f705d0d 100644 --- a/test/rbs/definition_builder_test.rb +++ b/test/rbs/definition_builder_test.rb @@ -3908,6 +3908,113 @@ def bar = "" end end + def test_inline_decl__visibility + SignatureManager.new do |manager| + manager.add_ruby_file("visibility.rb", <<~RUBY) + class Foo + def public_before = nil + + private + + def private_in_section = nil + attr_reader :private_attr + def self.singleton_in_private_section = nil + + public + + def public_after = nil + attr_writer :public_attr + + private def private_def = nil + public def public_def = nil + private attr_accessor :private_accessor + private def self.private_singleton = nil + end + RUBY + + manager.build do |env| + builder = DefinitionBuilder.new(env: env) + + builder.build_instance(type_name("::Foo")).tap do |definition| + assert_equal :public, definition.methods[:public_before].accessibility + assert_equal :private, definition.methods[:private_in_section].accessibility + assert_equal :private, definition.methods[:private_attr].accessibility + assert_equal :public, definition.methods[:public_after].accessibility + assert_equal :public, definition.methods[:public_attr=].accessibility + assert_equal :private, definition.methods[:private_def].accessibility + assert_equal :public, definition.methods[:public_def].accessibility + assert_equal :private, definition.methods[:private_accessor].accessibility + assert_equal :private, definition.methods[:private_accessor=].accessibility + end + + builder.build_singleton(type_name("::Foo")).tap do |definition| + # `private` without arguments doesn't affect singleton methods + assert_equal :public, definition.methods[:singleton_in_private_section].accessibility + assert_equal :private, definition.methods[:private_singleton].accessibility + end + end + end + end + + def test_inline_decl__visibility__public_def_in_private_section + SignatureManager.new do |manager| + manager.add_ruby_file("visibility.rb", <<~RUBY) + class Foo + private + + public def foo = nil + + def bar = nil + end + RUBY + + manager.build do |env| + builder = DefinitionBuilder.new(env: env) + + builder.build_instance(type_name("::Foo")).tap do |definition| + assert_equal :public, definition.methods[:foo].accessibility + assert_equal :private, definition.methods[:bar].accessibility + end + end + end + end + + def test_inline_decl__visibility__overriding + SignatureManager.new do |manager| + manager.files[Pathname("base.rbs")] = <<~RBS + class Base + def foo: () -> String + def bar: () -> String + end + RBS + + manager.add_ruby_file("child.rb", <<~RUBY) + class Child < Base + private def foo = "" + + # @rbs () -> String + private def bar = "" + end + RUBY + + manager.build do |env| + builder = DefinitionBuilder.new(env: env) + + builder.build_instance(type_name("::Child")).tap do |definition| + # Unannotated overriding inherits the type from the parent, but the visibility is given by the child + definition.methods[:foo].tap do |method| + assert_equal ["() -> ::String"], method.method_types.map(&:to_s) + assert_equal :private, method.accessibility + end + + definition.methods[:bar].tap do |method| + assert_equal :private, method.accessibility + end + end + end + end + end + def test_inline_decl__module_self SignatureManager.new do |manager| manager.files[Pathname("foo.rbs")] = <<~RBS diff --git a/test/rbs/inline_parser_test.rb b/test/rbs/inline_parser_test.rb index befd885f32..81252a3218 100644 --- a/test/rbs/inline_parser_test.rb +++ b/test/rbs/inline_parser_test.rb @@ -2275,4 +2275,290 @@ def test_error__module_alias_non_constant_without_type_name assert_empty result.declarations end + + def test_parse__visibility_members + result = parse(<<~RUBY) + class Foo + def foo; end + + private + + def bar; end + + public + + def baz; end + end + RUBY + + assert_empty result.diagnostics + + result.declarations[0].tap do |decl| + assert_instance_of RBS::AST::Ruby::Declarations::ClassDecl, decl + + assert_equal 5, decl.members.size + + decl.members[0].tap do |member| + assert_instance_of RBS::AST::Ruby::Members::DefMember, member + assert_equal :foo, member.name + assert_nil member.visibility + end + + decl.members[1].tap do |member| + assert_instance_of RBS::AST::Ruby::Members::PrivateMember, member + assert_equal :private, member.visibility + assert_equal "private", member.location.source + end + + decl.members[2].tap do |member| + assert_instance_of RBS::AST::Ruby::Members::DefMember, member + assert_equal :bar, member.name + assert_nil member.visibility + end + + decl.members[3].tap do |member| + assert_instance_of RBS::AST::Ruby::Members::PublicMember, member + assert_equal :public, member.visibility + assert_equal "public", member.location.source + end + + decl.members[4].tap do |member| + assert_instance_of RBS::AST::Ruby::Members::DefMember, member + assert_equal :baz, member.name + end + end + end + + def test_parse__visibility_members__in_module + result = parse(<<~RUBY) + module Foo + private + + def bar; end + end + RUBY + + assert_empty result.diagnostics + + result.declarations[0].tap do |decl| + assert_instance_of RBS::AST::Ruby::Declarations::ModuleDecl, decl + + assert_instance_of RBS::AST::Ruby::Members::PrivateMember, decl.members[0] + assert_instance_of RBS::AST::Ruby::Members::DefMember, decl.members[1] + end + end + + def test_parse__visibility_members__skip + result = parse(<<~RUBY) + class Foo + # @rbs skip + private + + def bar; end + end + RUBY + + assert_empty result.diagnostics + + result.declarations[0].tap do |decl| + assert_equal 1, decl.members.size + assert_instance_of RBS::AST::Ruby::Members::DefMember, decl.members[0] + end + end + + def test_parse__visibility_members__toplevel + result = parse(<<~RUBY) + private + + class Foo + end + RUBY + + assert_empty result.diagnostics + assert_equal 1, result.declarations.size + assert_instance_of RBS::AST::Ruby::Declarations::ClassDecl, result.declarations[0] + end + + def test_parse__def__with_visibility + result = parse(<<~RUBY) + class Foo + private def foo; end + + public def bar; end + + # @rbs (String) -> void + private def baz(x); end + + private def qux(x) #: Integer + x + end + + private def self.quux; end + end + RUBY + + assert_empty result.diagnostics + + result.declarations[0].tap do |decl| + assert_equal 5, decl.members.size + + decl.members[0].tap do |member| + assert_instance_of RBS::AST::Ruby::Members::DefMember, member + assert_equal :foo, member.name + assert_equal :private, member.visibility + assert_equal "def foo; end", member.location.source + end + + decl.members[1].tap do |member| + assert_instance_of RBS::AST::Ruby::Members::DefMember, member + assert_equal :bar, member.name + assert_equal :public, member.visibility + end + + decl.members[2].tap do |member| + assert_instance_of RBS::AST::Ruby::Members::DefMember, member + assert_equal :baz, member.name + assert_equal :private, member.visibility + assert_equal ["(String) -> void"], member.overloads.map { _1.method_type.to_s } + end + + decl.members[3].tap do |member| + assert_instance_of RBS::AST::Ruby::Members::DefMember, member + assert_equal :qux, member.name + assert_equal :private, member.visibility + assert_equal ["(untyped x) -> Integer"], member.overloads.map { _1.method_type.to_s } + end + + decl.members[4].tap do |member| + assert_instance_of RBS::AST::Ruby::Members::DefMember, member + assert_equal :quux, member.name + assert_predicate member, :singleton? + assert_equal :private, member.visibility + end + end + end + + def test_parse__def__with_visibility__skip + result = parse(<<~RUBY) + class Foo + # @rbs skip + private def foo; end + + private def bar; end + end + RUBY + + assert_empty result.diagnostics + + result.declarations[0].tap do |decl| + assert_equal 1, decl.members.size + assert_equal :bar, decl.members[0].name + assert_equal :private, decl.members[0].visibility + end + end + + def test_parse__def__with_visibility__nested_in_private_call + result = parse(<<~RUBY) + class Foo + private def foo; end + + def bar; end + end + RUBY + + assert_empty result.diagnostics + + result.declarations[0].tap do |decl| + # The visibility is reset after leaving the `private` call + assert_nil decl.members[1].visibility + end + end + + def test_error__def__with_visibility__toplevel + result = parse(<<~RUBY) + private def foo; end + RUBY + + assert_equal 1, result.diagnostics.size + assert_instance_of RBS::InlineParser::Diagnostic::TopLevelMethodDefinition, result.diagnostics[0] + end + + def test_parse__attr__with_visibility + result = parse(<<~RUBY) + class Foo + private attr_reader :foo #: String + + public attr_writer :bar + + private attr_accessor :baz, :qux + + attr_reader :quux + end + RUBY + + assert_empty result.diagnostics + + result.declarations[0].tap do |decl| + assert_equal 4, decl.members.size + + decl.members[0].tap do |member| + assert_instance_of RBS::AST::Ruby::Members::AttrReaderMember, member + assert_equal [:foo], member.names + assert_equal :private, member.visibility + assert_equal "String", member.type.to_s + end + + decl.members[1].tap do |member| + assert_instance_of RBS::AST::Ruby::Members::AttrWriterMember, member + assert_equal [:bar], member.names + assert_equal :public, member.visibility + end + + decl.members[2].tap do |member| + assert_instance_of RBS::AST::Ruby::Members::AttrAccessorMember, member + assert_equal [:baz, :qux], member.names + assert_equal :private, member.visibility + end + + decl.members[3].tap do |member| + assert_instance_of RBS::AST::Ruby::Members::AttrReaderMember, member + assert_nil member.visibility + end + end + end + + def test_parse__visibility__symbol_arguments_ignored + result = parse(<<~RUBY) + class Foo + def foo; end + + private :foo + end + RUBY + + assert_empty result.diagnostics + + result.declarations[0].tap do |decl| + assert_equal 1, decl.members.size + assert_instance_of RBS::AST::Ruby::Members::DefMember, decl.members[0] + assert_nil decl.members[0].visibility + end + end + + def test_parse__visibility__with_receiver_ignored + result = parse(<<~RUBY) + class Foo + self.private + + def foo; end + end + RUBY + + assert_empty result.diagnostics + + result.declarations[0].tap do |decl| + assert_equal 1, decl.members.size + assert_instance_of RBS::AST::Ruby::Members::DefMember, decl.members[0] + end + end end diff --git a/test/rbs/inline_type_fingerprint_test.rb b/test/rbs/inline_type_fingerprint_test.rb index 1922d6f77a..abbaf29bc9 100644 --- a/test/rbs/inline_type_fingerprint_test.rb +++ b/test/rbs/inline_type_fingerprint_test.rb @@ -299,4 +299,68 @@ def test_class_alias_fingerprint assert_equal result, result2 refute_equal result, result3 end + + def test_visibility_fingerprint + result = fingerprint(<<-RUBY) +class Foo + private + + def bar + end +end + RUBY + + result2 = fingerprint(<<-RUBY) +class Foo + private + def bar + end +end + RUBY + + result3 = fingerprint(<<-RUBY) +class Foo + def bar + end +end + RUBY + + result4 = fingerprint(<<-RUBY) +class Foo + private def bar + end +end + RUBY + + result5 = fingerprint(<<-RUBY) +class Foo + public + + def bar + end +end + RUBY + + assert_equal result, result2 + refute_equal result, result3 + refute_equal result, result4 + refute_equal result3, result4 + refute_equal result, result5 + end + + def test_attribute_visibility_fingerprint + result = fingerprint(<<-RUBY) +class Foo + private attr_reader :bar +end + RUBY + + result2 = fingerprint(<<-RUBY) +class Foo + attr_reader :bar +end + RUBY + + refute_equal result, result2 + end end