Skip to content

Commit 4d807c7

Browse files
kcdragonandyw8
andauthored
Support private_class_method in the indexer (#2858)
* 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]>
1 parent 5a36d78 commit 4d807c7

File tree

2 files changed

+123
-1
lines changed

2 files changed

+123
-1
lines changed

lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,9 @@ def on_call_node_enter(node)
282282
@visibility_stack.push(Entry::Visibility::PRIVATE)
283283
when :module_function
284284
handle_module_function(node)
285+
when :private_class_method
286+
@visibility_stack.push(Entry::Visibility::PRIVATE)
287+
handle_private_class_method(node)
285288
end
286289

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

881+
sig { params(node: Prism::CallNode).void }
882+
def handle_private_class_method(node)
883+
node.arguments&.arguments&.each do |argument|
884+
string_or_symbol_nodes = case argument
885+
when Prism::StringNode, Prism::SymbolNode
886+
[argument]
887+
when Prism::ArrayNode
888+
argument.elements
889+
else
890+
[]
891+
end
892+
893+
unless string_or_symbol_nodes.empty?
894+
# pop the visibility off since there isn't a method definition following `private_class_method`
895+
@visibility_stack.pop
896+
end
897+
898+
string_or_symbol_nodes.each do |string_or_symbol_node|
899+
method_name = case string_or_symbol_node
900+
when Prism::StringNode
901+
string_or_symbol_node.content
902+
when Prism::SymbolNode
903+
string_or_symbol_node.value
904+
end
905+
next unless method_name
906+
907+
owner_name = @owner_stack.last&.name
908+
next unless owner_name
909+
910+
entries = @index.resolve_method(method_name, @index.existing_or_new_singleton_class(owner_name).name)
911+
next unless entries
912+
913+
entries.each do |entry|
914+
entry.visibility = Entry::Visibility::PRIVATE
915+
end
916+
end
917+
end
918+
end
919+
878920
sig { returns(Entry::Visibility) }
879921
def current_visibility
880922
T.must(@visibility_stack.last)

lib/ruby_indexer/test/method_test.rb

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,86 @@ def bar; end
149149
end
150150
end
151151

152+
def test_private_class_method_visibility_tracking_string_symbol_arguments
153+
index(<<~RUBY)
154+
class Test
155+
def self.foo
156+
end
157+
158+
def self.bar
159+
end
160+
161+
private_class_method("foo", :bar)
162+
163+
def self.baz
164+
end
165+
end
166+
RUBY
167+
168+
["foo", "bar"].each do |keyword|
169+
entries = T.must(@index[keyword])
170+
assert_equal(1, entries.size)
171+
entry = entries.first
172+
assert_predicate(entry, :private?)
173+
end
174+
175+
entries = T.must(@index["baz"])
176+
assert_equal(1, entries.size)
177+
entry = entries.first
178+
assert_predicate(entry, :public?)
179+
end
180+
181+
def test_private_class_method_visibility_tracking_array_argument
182+
index(<<~RUBY)
183+
class Test
184+
def self.foo
185+
end
186+
187+
def self.bar
188+
end
189+
190+
private_class_method(["foo", :bar])
191+
192+
def self.baz
193+
end
194+
end
195+
RUBY
196+
197+
["foo", "bar"].each do |keyword|
198+
entries = T.must(@index[keyword])
199+
assert_equal(1, entries.size)
200+
entry = entries.first
201+
assert_predicate(entry, :private?)
202+
end
203+
204+
entries = T.must(@index["baz"])
205+
assert_equal(1, entries.size)
206+
entry = entries.first
207+
assert_predicate(entry, :public?)
208+
end
209+
210+
def test_private_class_method_visibility_tracking_method_argument
211+
index(<<~RUBY)
212+
class Test
213+
private_class_method def self.foo
214+
end
215+
216+
def self.bar
217+
end
218+
end
219+
RUBY
220+
221+
entries = T.must(@index["foo"])
222+
assert_equal(1, entries.size)
223+
entry = entries.first
224+
assert_predicate(entry, :private?)
225+
226+
entries = T.must(@index["bar"])
227+
assert_equal(1, entries.size)
228+
entry = entries.first
229+
assert_predicate(entry, :public?)
230+
end
231+
152232
def test_comments_documentation
153233
index(<<~RUBY)
154234
# Documentation for Foo

0 commit comments

Comments
 (0)