Skip to content

Commit

Permalink
Support private_class_method in the indexer (#2858)
Browse files Browse the repository at this point in the history
* Support `private_class_method` in the indexer

Fixes #2781

* Apply suggestions from code review

Co-authored-by: Andy Waite <[email protected]>

* Fix tests

* Remove unnecessary conditional

* Support `private_class_method` being called with a method definition

---------

Co-authored-by: Andy Waite <[email protected]>
  • Loading branch information
kcdragon and andyw8 authored Nov 21, 2024
1 parent 5a36d78 commit 4d807c7
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 1 deletion.
44 changes: 43 additions & 1 deletion lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,9 @@ def on_call_node_enter(node)
@visibility_stack.push(Entry::Visibility::PRIVATE)
when :module_function
handle_module_function(node)
when :private_class_method
@visibility_stack.push(Entry::Visibility::PRIVATE)
handle_private_class_method(node)
end

@enhancements.each do |enhancement|
Expand All @@ -297,7 +300,7 @@ def on_call_node_enter(node)
def on_call_node_leave(node)
message = node.name
case message
when :public, :protected, :private
when :public, :protected, :private, :private_class_method
# We want to restore the visibility stack when we leave a method definition with a visibility modifier
# e.g. `private def foo; end`
if node.arguments&.arguments&.first&.is_a?(Prism::DefNode)
Expand Down Expand Up @@ -875,6 +878,45 @@ def handle_module_function(node)
end
end

sig { params(node: Prism::CallNode).void }
def handle_private_class_method(node)
node.arguments&.arguments&.each do |argument|
string_or_symbol_nodes = case argument
when Prism::StringNode, Prism::SymbolNode
[argument]
when Prism::ArrayNode
argument.elements
else
[]
end

unless string_or_symbol_nodes.empty?
# pop the visibility off since there isn't a method definition following `private_class_method`
@visibility_stack.pop
end

string_or_symbol_nodes.each do |string_or_symbol_node|
method_name = case string_or_symbol_node
when Prism::StringNode
string_or_symbol_node.content
when Prism::SymbolNode
string_or_symbol_node.value
end
next unless method_name

owner_name = @owner_stack.last&.name
next unless owner_name

entries = @index.resolve_method(method_name, @index.existing_or_new_singleton_class(owner_name).name)
next unless entries

entries.each do |entry|
entry.visibility = Entry::Visibility::PRIVATE
end
end
end
end

sig { returns(Entry::Visibility) }
def current_visibility
T.must(@visibility_stack.last)
Expand Down
80 changes: 80 additions & 0 deletions lib/ruby_indexer/test/method_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,86 @@ def bar; end
end
end

def test_private_class_method_visibility_tracking_string_symbol_arguments
index(<<~RUBY)
class Test
def self.foo
end
def self.bar
end
private_class_method("foo", :bar)
def self.baz
end
end
RUBY

["foo", "bar"].each do |keyword|
entries = T.must(@index[keyword])
assert_equal(1, entries.size)
entry = entries.first
assert_predicate(entry, :private?)
end

entries = T.must(@index["baz"])
assert_equal(1, entries.size)
entry = entries.first
assert_predicate(entry, :public?)
end

def test_private_class_method_visibility_tracking_array_argument
index(<<~RUBY)
class Test
def self.foo
end
def self.bar
end
private_class_method(["foo", :bar])
def self.baz
end
end
RUBY

["foo", "bar"].each do |keyword|
entries = T.must(@index[keyword])
assert_equal(1, entries.size)
entry = entries.first
assert_predicate(entry, :private?)
end

entries = T.must(@index["baz"])
assert_equal(1, entries.size)
entry = entries.first
assert_predicate(entry, :public?)
end

def test_private_class_method_visibility_tracking_method_argument
index(<<~RUBY)
class Test
private_class_method def self.foo
end
def self.bar
end
end
RUBY

entries = T.must(@index["foo"])
assert_equal(1, entries.size)
entry = entries.first
assert_predicate(entry, :private?)

entries = T.must(@index["bar"])
assert_equal(1, entries.size)
entry = entries.first
assert_predicate(entry, :public?)
end

def test_comments_documentation
index(<<~RUBY)
# Documentation for Foo
Expand Down

0 comments on commit 4d807c7

Please sign in to comment.