diff --git a/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb b/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb index 0295e4cf2..8f63c1b48 100644 --- a/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +++ b/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb @@ -80,6 +80,11 @@ def initialize(index, dispatcher, parse_result, uri, collect_comments: false) :on_instance_variable_or_write_node_enter, :on_instance_variable_target_node_enter, :on_alias_method_node_enter, + :on_class_variable_and_write_node_enter, + :on_class_variable_operator_write_node_enter, + :on_class_variable_or_write_node_enter, + :on_class_variable_target_node_enter, + :on_class_variable_write_node_enter, ) end @@ -434,6 +439,31 @@ def on_alias_method_node_enter(node) ) end + sig { params(node: Prism::ClassVariableAndWriteNode).void } + def on_class_variable_and_write_node_enter(node) + handle_class_variable(node, node.name_loc) + end + + sig { params(node: Prism::ClassVariableOperatorWriteNode).void } + def on_class_variable_operator_write_node_enter(node) + handle_class_variable(node, node.name_loc) + end + + sig { params(node: Prism::ClassVariableOrWriteNode).void } + def on_class_variable_or_write_node_enter(node) + handle_class_variable(node, node.name_loc) + end + + sig { params(node: Prism::ClassVariableTargetNode).void } + def on_class_variable_target_node_enter(node) + handle_class_variable(node, node.location) + end + + sig { params(node: Prism::ClassVariableWriteNode).void } + def on_class_variable_write_node_enter(node) + handle_class_variable(node, node.name_loc) + end + sig do params( name: String, @@ -552,6 +582,42 @@ def handle_global_variable(node, loc) )) end + sig do + params( + node: T.any( + Prism::ClassVariableAndWriteNode, + Prism::ClassVariableOperatorWriteNode, + Prism::ClassVariableOrWriteNode, + Prism::ClassVariableTargetNode, + Prism::ClassVariableWriteNode, + ), + loc: Prism::Location, + ).void + end + def handle_class_variable(node, loc) + name = node.name.to_s + # Ignore incomplete class variable names, which aren't valid Ruby syntax. + # This could occur if the code is in an incomplete or temporary state. + return if name == "@@" + + comments = collect_comments(node) + + owner = @owner_stack.last + + # set the class variable's owner to the attached context when defined within a singleton scope. + if owner.is_a?(Entry::SingletonClass) + owner = @owner_stack.reverse.find { |entry| !entry.name.include?("", @type_inferrer.infer_receiver_type(node_context).name) end + def test_infer_receiver_type_class_variables_in_class_body + node_context = index_and_locate(<<~RUBY, { line: 1, character: 2 }) + class Foo + @@hello1 + end + RUBY + + assert_equal("Foo", @type_inferrer.infer_receiver_type(node_context).name) + end + + def test_infer_receiver_type_class_variables_in_singleton_method + node_context = index_and_locate(<<~RUBY, { line: 2, character: 4 }) + class Foo + def self.bar + @@hello1 + end + end + RUBY + + assert_equal("Foo", @type_inferrer.infer_receiver_type(node_context).name) + end + + def test_infer_receiver_type_class_variables_in_singleton_block_body + node_context = index_and_locate(<<~RUBY, { line: 2, character: 4 }) + class Foo + class << self + @@hello1 + end + end + RUBY + + assert_equal("Foo", @type_inferrer.infer_receiver_type(node_context).name) + end + + def test_infer_receiver_type_class_variables_in_singleton_block_method + node_context = index_and_locate(<<~RUBY, { line: 3, character: 6 }) + class Foo + class << self + def bar + @@hello1 + end + end + end + RUBY + + assert_equal("Foo", @type_inferrer.infer_receiver_type(node_context).name) + end + + def test_infer_receiver_type_class_variables_in_instance_method + node_context = index_and_locate(<<~RUBY, { line: 2, character: 4 }) + class Foo + def bar + @@hello1 + end + end + RUBY + + assert_equal("Foo", @type_inferrer.infer_receiver_type(node_context).name) + end + + def test_infer_top_level_class_variables + node_context = index_and_locate(<<~RUBY, { line: 0, character: 0 }) + @@foo + RUBY + + assert_equal("Object", @type_inferrer.infer_receiver_type(node_context).name) + end + private def index_and_locate(source, position)