diff --git a/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb b/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb deleted file mode 100644 index 8c167c40a..000000000 --- a/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +++ /dev/null @@ -1,335 +0,0 @@ -# typed: strict -# frozen_string_literal: true - -module RubyIndexer - class ReferenceFinder - # @abstract - class Target; end - - class ConstTarget < Target - #: String - attr_reader :fully_qualified_name - - #: (String fully_qualified_name) -> void - def initialize(fully_qualified_name) - super() - @fully_qualified_name = fully_qualified_name - end - end - - class MethodTarget < Target - #: String - attr_reader :method_name - - #: (String method_name) -> void - def initialize(method_name) - super() - @method_name = method_name - end - end - - class InstanceVariableTarget < Target - #: String - attr_reader :name - - #: Array[String] - attr_reader :owner_ancestors - - #: (String name, Array[String] owner_ancestors) -> void - def initialize(name, owner_ancestors) - super() - @name = name - @owner_ancestors = owner_ancestors - end - end - - class Reference - #: String - attr_reader :name - - #: Prism::Location - attr_reader :location - - #: bool - attr_reader :declaration - - #: (String name, Prism::Location location, declaration: bool) -> void - def initialize(name, location, declaration:) - @name = name - @location = location - @declaration = declaration - end - end - - #: (Target target, RubyIndexer::Index index, Prism::Dispatcher dispatcher, URI::Generic uri, ?include_declarations: bool) -> void - def initialize(target, index, dispatcher, uri, include_declarations: true) - @target = target - @index = index - @uri = uri - @include_declarations = include_declarations - @stack = [] #: Array[String] - @references = [] #: Array[Reference] - - dispatcher.register( - self, - :on_class_node_enter, - :on_class_node_leave, - :on_module_node_enter, - :on_module_node_leave, - :on_singleton_class_node_enter, - :on_singleton_class_node_leave, - :on_def_node_enter, - :on_def_node_leave, - :on_multi_write_node_enter, - :on_constant_path_write_node_enter, - :on_constant_path_or_write_node_enter, - :on_constant_path_operator_write_node_enter, - :on_constant_path_and_write_node_enter, - :on_constant_or_write_node_enter, - :on_constant_path_node_enter, - :on_constant_read_node_enter, - :on_constant_write_node_enter, - :on_constant_or_write_node_enter, - :on_constant_and_write_node_enter, - :on_constant_operator_write_node_enter, - :on_instance_variable_read_node_enter, - :on_instance_variable_write_node_enter, - :on_instance_variable_and_write_node_enter, - :on_instance_variable_operator_write_node_enter, - :on_instance_variable_or_write_node_enter, - :on_instance_variable_target_node_enter, - :on_call_node_enter, - ) - end - - #: -> Array[Reference] - def references - return @references if @include_declarations - - @references.reject(&:declaration) - end - - #: (Prism::ClassNode node) -> void - def on_class_node_enter(node) - @stack << node.constant_path.slice - end - - #: (Prism::ClassNode node) -> void - def on_class_node_leave(node) - @stack.pop - end - - #: (Prism::ModuleNode node) -> void - def on_module_node_enter(node) - @stack << node.constant_path.slice - end - - #: (Prism::ModuleNode node) -> void - def on_module_node_leave(node) - @stack.pop - end - - #: (Prism::SingletonClassNode node) -> void - def on_singleton_class_node_enter(node) - expression = node.expression - return unless expression.is_a?(Prism::SelfNode) - - @stack << "<#{@stack.last}>" - end - - #: (Prism::SingletonClassNode node) -> void - def on_singleton_class_node_leave(node) - @stack.pop - end - - #: (Prism::ConstantPathNode node) -> void - def on_constant_path_node_enter(node) - name = Index.constant_name(node) - return unless name - - collect_constant_references(name, node.location) - end - - #: (Prism::ConstantReadNode node) -> void - def on_constant_read_node_enter(node) - name = Index.constant_name(node) - return unless name - - collect_constant_references(name, node.location) - end - - #: (Prism::MultiWriteNode node) -> void - def on_multi_write_node_enter(node) - [*node.lefts, *node.rest, *node.rights].each do |target| - case target - when Prism::ConstantTargetNode, Prism::ConstantPathTargetNode - collect_constant_references(target.name.to_s, target.location) - end - end - end - - #: (Prism::ConstantPathWriteNode node) -> void - def on_constant_path_write_node_enter(node) - target = node.target - return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode) - - name = Index.constant_name(target) - return unless name - - collect_constant_references(name, target.location) - end - - #: (Prism::ConstantPathOrWriteNode node) -> void - def on_constant_path_or_write_node_enter(node) - target = node.target - return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode) - - name = Index.constant_name(target) - return unless name - - collect_constant_references(name, target.location) - end - - #: (Prism::ConstantPathOperatorWriteNode node) -> void - def on_constant_path_operator_write_node_enter(node) - target = node.target - return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode) - - name = Index.constant_name(target) - return unless name - - collect_constant_references(name, target.location) - end - - #: (Prism::ConstantPathAndWriteNode node) -> void - def on_constant_path_and_write_node_enter(node) - target = node.target - return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode) - - name = Index.constant_name(target) - return unless name - - collect_constant_references(name, target.location) - end - - #: (Prism::ConstantWriteNode node) -> void - def on_constant_write_node_enter(node) - collect_constant_references(node.name.to_s, node.name_loc) - end - - #: (Prism::ConstantOrWriteNode node) -> void - def on_constant_or_write_node_enter(node) - collect_constant_references(node.name.to_s, node.name_loc) - end - - #: (Prism::ConstantAndWriteNode node) -> void - def on_constant_and_write_node_enter(node) - collect_constant_references(node.name.to_s, node.name_loc) - end - - #: (Prism::ConstantOperatorWriteNode node) -> void - def on_constant_operator_write_node_enter(node) - collect_constant_references(node.name.to_s, node.name_loc) - end - - #: (Prism::DefNode node) -> void - def on_def_node_enter(node) - if @target.is_a?(MethodTarget) && (name = node.name.to_s) == @target.method_name - @references << Reference.new(name, node.name_loc, declaration: true) - end - - if node.receiver.is_a?(Prism::SelfNode) - @stack << "<#{@stack.last}>" - end - end - - #: (Prism::DefNode node) -> void - def on_def_node_leave(node) - if node.receiver.is_a?(Prism::SelfNode) - @stack.pop - end - end - - #: (Prism::InstanceVariableReadNode node) -> void - def on_instance_variable_read_node_enter(node) - collect_instance_variable_references(node.name.to_s, node.location, false) - end - - #: (Prism::InstanceVariableWriteNode node) -> void - def on_instance_variable_write_node_enter(node) - collect_instance_variable_references(node.name.to_s, node.name_loc, true) - end - - #: (Prism::InstanceVariableAndWriteNode node) -> void - def on_instance_variable_and_write_node_enter(node) - collect_instance_variable_references(node.name.to_s, node.name_loc, true) - end - - #: (Prism::InstanceVariableOperatorWriteNode node) -> void - def on_instance_variable_operator_write_node_enter(node) - collect_instance_variable_references(node.name.to_s, node.name_loc, true) - end - - #: (Prism::InstanceVariableOrWriteNode node) -> void - def on_instance_variable_or_write_node_enter(node) - collect_instance_variable_references(node.name.to_s, node.name_loc, true) - end - - #: (Prism::InstanceVariableTargetNode node) -> void - def on_instance_variable_target_node_enter(node) - collect_instance_variable_references(node.name.to_s, node.location, true) - end - - #: (Prism::CallNode node) -> void - def on_call_node_enter(node) - if @target.is_a?(MethodTarget) && (name = node.name.to_s) == @target.method_name - @references << Reference.new( - name, - node.message_loc, #: as !nil - declaration: false, - ) - end - end - - private - - #: (String name, Prism::Location location) -> void - def collect_constant_references(name, location) - return unless @target.is_a?(ConstTarget) - - entries = @index.resolve(name, @stack) - return unless entries - - # Filter down to all constant declarations that match the expected name and type - matching_entries = entries.select do |e| - [ - Entry::Namespace, - Entry::Constant, - Entry::ConstantAlias, - Entry::UnresolvedConstantAlias, - ].any? { |klass| e.is_a?(klass) } && - e.name == @target.fully_qualified_name - end - - return if matching_entries.empty? - - # If any of the matching entries have the same location as the constant and were - # defined in the same file, then it is that constant's declaration - declaration = matching_entries.any? do |e| - e.uri == @uri && e.name_location == location - end - - @references << Reference.new(name, location, declaration: declaration) - end - - #: (String name, Prism::Location location, bool declaration) -> void - def collect_instance_variable_references(name, location, declaration) - return unless @target.is_a?(InstanceVariableTarget) && name == @target.name - - receiver_type = Index.actual_nesting(@stack, nil).join("::") - if @target.owner_ancestors.include?(receiver_type) - @references << Reference.new(name, location, declaration: declaration) - end - end - end -end diff --git a/lib/ruby_indexer/ruby_indexer.rb b/lib/ruby_indexer/ruby_indexer.rb index 3646da7b9..f20a451f6 100644 --- a/lib/ruby_indexer/ruby_indexer.rb +++ b/lib/ruby_indexer/ruby_indexer.rb @@ -7,7 +7,6 @@ require "ruby_indexer/lib/ruby_indexer/uri" require "ruby_indexer/lib/ruby_indexer/visibility_scope" require "ruby_indexer/lib/ruby_indexer/declaration_listener" -require "ruby_indexer/lib/ruby_indexer/reference_finder" require "ruby_indexer/lib/ruby_indexer/enhancement" require "ruby_indexer/lib/ruby_indexer/index" require "ruby_indexer/lib/ruby_indexer/entry" diff --git a/lib/ruby_indexer/test/reference_finder_test.rb b/lib/ruby_indexer/test/reference_finder_test.rb deleted file mode 100644 index 45be86db4..000000000 --- a/lib/ruby_indexer/test/reference_finder_test.rb +++ /dev/null @@ -1,395 +0,0 @@ -# typed: true -# frozen_string_literal: true - -require "test_helper" - -module RubyIndexer - class ReferenceFinderTest < Minitest::Test - def test_finds_constant_references - refs = find_const_references("Foo::Bar", <<~RUBY) - module Foo - class Bar - end - - Bar - end - - Foo::Bar - RUBY - - assert_equal("Bar", refs[0].name) - assert_equal(2, refs[0].location.start_line) - - assert_equal("Bar", refs[1].name) - assert_equal(5, refs[1].location.start_line) - - assert_equal("Foo::Bar", refs[2].name) - assert_equal(8, refs[2].location.start_line) - end - - def test_finds_constant_references_inside_singleton_contexts - refs = find_const_references("Foo::::Bar", <<~RUBY) - class Foo - class << self - class Bar - end - - Bar - end - end - RUBY - - assert_equal("Bar", refs[0].name) - assert_equal(3, refs[0].location.start_line) - - assert_equal("Bar", refs[1].name) - assert_equal(6, refs[1].location.start_line) - end - - def test_finds_top_level_constant_references - refs = find_const_references("Bar", <<~RUBY) - class Bar - end - - class Foo - ::Bar - - class << self - ::Bar - end - end - RUBY - - assert_equal("Bar", refs[0].name) - assert_equal(1, refs[0].location.start_line) - - assert_equal("::Bar", refs[1].name) - assert_equal(5, refs[1].location.start_line) - - assert_equal("::Bar", refs[2].name) - assert_equal(8, refs[2].location.start_line) - end - - def test_finds_method_references - refs = find_method_references("foo", <<~RUBY) - class Bar - def foo - end - - def baz - foo - end - end - RUBY - - assert_equal(2, refs.size) - - assert_equal("foo", refs[0].name) - assert_equal(2, refs[0].location.start_line) - - assert_equal("foo", refs[1].name) - assert_equal(6, refs[1].location.start_line) - end - - def test_does_not_mismatch_on_readers_and_writers - refs = find_method_references("foo", <<~RUBY) - class Bar - def foo - end - - def foo=(value) - end - - def baz - self.foo = 1 - self.foo - end - end - RUBY - - # We want to match `foo` but not `foo=` - assert_equal(2, refs.size) - - assert_equal("foo", refs[0].name) - assert_equal(2, refs[0].location.start_line) - - assert_equal("foo", refs[1].name) - assert_equal(10, refs[1].location.start_line) - end - - def test_matches_writers - refs = find_method_references("foo=", <<~RUBY) - class Bar - def foo - end - - def foo=(value) - end - - def baz - self.foo = 1 - self.foo - end - end - RUBY - - # We want to match `foo=` but not `foo` - assert_equal(2, refs.size) - - assert_equal("foo=", refs[0].name) - assert_equal(5, refs[0].location.start_line) - - assert_equal("foo=", refs[1].name) - assert_equal(9, refs[1].location.start_line) - end - - def test_find_inherited_methods - refs = find_method_references("foo", <<~RUBY) - class Bar - def foo - end - end - - class Baz < Bar - super.foo - end - RUBY - - assert_equal(2, refs.size) - - assert_equal("foo", refs[0].name) - assert_equal(2, refs[0].location.start_line) - - assert_equal("foo", refs[1].name) - assert_equal(7, refs[1].location.start_line) - end - - def test_finds_methods_created_in_mixins - refs = find_method_references("foo", <<~RUBY) - module Mixin - def foo - end - end - - class Bar - include Mixin - end - - Bar.foo - RUBY - - assert_equal(2, refs.size) - - assert_equal("foo", refs[0].name) - assert_equal(2, refs[0].location.start_line) - - assert_equal("foo", refs[1].name) - assert_equal(10, refs[1].location.start_line) - end - - def test_finds_singleton_methods - # The current implementation matches on both `Bar.foo` and `Bar#foo` even though they are different - - refs = find_method_references("foo", <<~RUBY) - class Bar - class << self - def foo - end - end - - def foo - end - end - - Bar.foo - RUBY - - assert_equal(3, refs.size) - - assert_equal("foo", refs[0].name) - assert_equal(3, refs[0].location.start_line) - - assert_equal("foo", refs[1].name) - assert_equal(7, refs[1].location.start_line) - - assert_equal("foo", refs[2].name) - assert_equal(11, refs[2].location.start_line) - end - - def test_finds_instance_variable_references - refs = find_instance_variable_references("@name", ["Foo"], <<~RUBY) - class Foo - def initialize - @name = "foo" - end - def name - @name - end - def name_capital - @name[0] - end - end - - class Bar - def initialize - @name = "foo" - end - def name - @name - end - end - RUBY - assert_equal(3, refs.size) - - assert_equal("@name", refs[0].name) - assert_equal(3, refs[0].location.start_line) - - assert_equal("@name", refs[1].name) - assert_equal(6, refs[1].location.start_line) - - assert_equal("@name", refs[2].name) - assert_equal(9, refs[2].location.start_line) - end - - def test_finds_instance_variable_write_references - refs = find_instance_variable_references("@foo", ["Foo"], <<~RUBY) - class Foo - def write - @foo = 1 - @foo &&= 2 - @foo ||= 3 - @foo += 4 - @foo, @bar = [] - end - end - RUBY - assert_equal(5, refs.size) - - assert_equal(["@foo"], refs.map(&:name).uniq) - assert_equal(3, refs[0].location.start_line) - assert_equal(4, refs[1].location.start_line) - assert_equal(5, refs[2].location.start_line) - assert_equal(6, refs[3].location.start_line) - assert_equal(7, refs[4].location.start_line) - end - - def test_finds_instance_variable_references_in_owner_ancestors - refs = find_instance_variable_references("@name", ["Foo", "Base", "Top", "Parent"], <<~RUBY) - module Base - def change_name(name) - @name = name - end - def name - @name - end - - module ::Top - def name - @name - end - end - end - - class Parent - def initialize - @name = "parent" - end - def name_capital - @name[0] - end - end - - class Foo < Parent - include Base - def initialize - @name = "foo" - end - def name - @name - end - end - - class Bar - def name - @name = "bar" - end - end - RUBY - assert_equal(7, refs.size) - - assert_equal("@name", refs[0].name) - assert_equal(3, refs[0].location.start_line) - - assert_equal("@name", refs[1].name) - assert_equal(6, refs[1].location.start_line) - - assert_equal("@name", refs[2].name) - assert_equal(11, refs[2].location.start_line) - - assert_equal("@name", refs[3].name) - assert_equal(18, refs[3].location.start_line) - - assert_equal("@name", refs[4].name) - assert_equal(21, refs[4].location.start_line) - - assert_equal("@name", refs[5].name) - assert_equal(28, refs[5].location.start_line) - - assert_equal("@name", refs[6].name) - assert_equal(31, refs[6].location.start_line) - end - - def test_accounts_for_reopened_classes - refs = find_const_references("Foo", <<~RUBY) - class Foo - end - class Foo - class Bar - end - end - - Foo.new - RUBY - - assert_equal(3, refs.size) - - assert_equal("Foo", refs[0].name) - assert_equal(1, refs[0].location.start_line) - - assert_equal("Foo", refs[1].name) - assert_equal(3, refs[1].location.start_line) - - assert_equal("Foo", refs[2].name) - assert_equal(8, refs[2].location.start_line) - end - - private - - def find_const_references(const_name, source) - target = ReferenceFinder::ConstTarget.new(const_name) - find_references(target, source) - end - - def find_method_references(method_name, source) - target = ReferenceFinder::MethodTarget.new(method_name) - find_references(target, source) - end - - def find_instance_variable_references(instance_variable_name, owner_ancestors, source) - target = ReferenceFinder::InstanceVariableTarget.new(instance_variable_name, owner_ancestors) - find_references(target, source) - end - - def find_references(target, source) - file_path = "/fake.rb" - uri = URI::Generic.from_path(path: file_path) - index = Index.new - index.index_single(uri, source) - parse_result = Prism.parse(source) - dispatcher = Prism::Dispatcher.new - finder = ReferenceFinder.new(target, index, dispatcher, uri) - dispatcher.visit(parse_result.value) - finder.references - end - end -end diff --git a/lib/ruby_lsp/requests/references.rb b/lib/ruby_lsp/requests/references.rb index f400224a4..332dca101 100644 --- a/lib/ruby_lsp/requests/references.rb +++ b/lib/ruby_lsp/requests/references.rb @@ -9,126 +9,226 @@ module Requests class References < Request include Support::Common + MAX_NUMBER_OF_METHOD_CANDIDATES_WITHOUT_RECEIVER = 30 + #: (GlobalState global_state, Store store, (RubyDocument | ERBDocument) document, Hash[Symbol, untyped] params) -> void def initialize(global_state, store, document, params) super() @global_state = global_state + @type_inferrer = global_state.type_inferrer #: TypeInferrer + @graph = global_state.graph #: Rubydex::Graph @store = store @document = document @params = params @locations = [] #: Array[Interface::Location] + @char_position = 0 #: Integer end # @override #: -> Array[Interface::Location] def perform - position = @params[:position] - char_position, _ = @document.find_index_by_position(position) + include_declarations = @params.dig(:context, :includeDeclaration) || false + @char_position, _ = @document.find_index_by_position(@params[:position]) node_context = RubyDocument.locate( @document.ast, - char_position, + @char_position, node_types: [ Prism::ConstantReadNode, Prism::ConstantPathNode, Prism::ConstantPathTargetNode, + Prism::ConstantAndWriteNode, + Prism::ConstantOperatorWriteNode, + Prism::ConstantOrWriteNode, + Prism::ConstantTargetNode, + Prism::ConstantWriteNode, Prism::InstanceVariableAndWriteNode, Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableReadNode, Prism::InstanceVariableTargetNode, Prism::InstanceVariableWriteNode, + Prism::ClassVariableAndWriteNode, + Prism::ClassVariableOperatorWriteNode, + Prism::ClassVariableOrWriteNode, + Prism::ClassVariableReadNode, + Prism::ClassVariableTargetNode, + Prism::ClassVariableWriteNode, + Prism::GlobalVariableAndWriteNode, + Prism::GlobalVariableOperatorWriteNode, + Prism::GlobalVariableOrWriteNode, + Prism::GlobalVariableReadNode, + Prism::GlobalVariableTargetNode, + Prism::GlobalVariableWriteNode, Prism::CallNode, + Prism::CallAndWriteNode, + Prism::CallOperatorWriteNode, + Prism::CallOrWriteNode, Prism::DefNode, ], code_units_cache: @document.code_units_cache, ) target = node_context.node - parent = node_context.parent return @locations if !target || target.is_a?(Prism::ProgramNode) - if target.is_a?(Prism::ConstantReadNode) && parent.is_a?(Prism::ConstantPathNode) - target = determine_target( - target, - parent, - position, - ) + case target + when Prism::ConstantReadNode, Prism::ConstantPathNode, Prism::ConstantPathTargetNode + name = constant_name(target) + handle_constant_references(name, node_context, include_declarations) if name + when Prism::ConstantTargetNode + handle_constant_references(target.name.to_s, node_context, include_declarations) + when Prism::ConstantAndWriteNode, Prism::ConstantOperatorWriteNode, Prism::ConstantOrWriteNode, + Prism::ConstantWriteNode + if cursor_on_name?(target.name_loc) + handle_constant_references(target.name.to_s, node_context, include_declarations) + end + when Prism::InstanceVariableReadNode, Prism::InstanceVariableTargetNode, + Prism::ClassVariableReadNode, Prism::ClassVariableTargetNode + handle_variable_references(target.name.to_s, node_context, include_declarations) + when Prism::InstanceVariableAndWriteNode, Prism::InstanceVariableOperatorWriteNode, + Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableWriteNode, + Prism::ClassVariableAndWriteNode, Prism::ClassVariableOperatorWriteNode, + Prism::ClassVariableOrWriteNode, Prism::ClassVariableWriteNode + if cursor_on_name?(target.name_loc) + handle_variable_references(target.name.to_s, node_context, include_declarations) + end + when Prism::GlobalVariableReadNode, Prism::GlobalVariableTargetNode + handle_global_variable_references(target.name.to_s, include_declarations) + when Prism::GlobalVariableAndWriteNode, Prism::GlobalVariableOperatorWriteNode, + Prism::GlobalVariableOrWriteNode, Prism::GlobalVariableWriteNode + if cursor_on_name?(target.name_loc) + handle_global_variable_references(target.name.to_s, include_declarations) + end + when Prism::CallNode + message_loc = target.message_loc + message = target.message + if message && message_loc && cursor_on_name?(message_loc) + resolve_method_references(message, node_context, include_declarations) + end + when Prism::CallAndWriteNode, Prism::CallOperatorWriteNode, Prism::CallOrWriteNode + message_loc = target.message_loc + if message_loc && cursor_on_name?(message_loc) + resolve_method_references(target.read_name.to_s, node_context, include_declarations) + end + when Prism::DefNode + handle_def_node_references(target, node_context, include_declarations) if cursor_on_name?(target.name_loc) end - target = target #: as Prism::ConstantReadNode | Prism::ConstantPathNode | Prism::ConstantPathTargetNode | Prism::InstanceVariableAndWriteNode | Prism::InstanceVariableOperatorWriteNode | Prism::InstanceVariableOrWriteNode | Prism::InstanceVariableReadNode | Prism::InstanceVariableTargetNode | Prism::InstanceVariableWriteNode | Prism::CallNode | Prism::DefNode, + @locations + end - reference_target = create_reference_target(target, node_context) - return @locations unless reference_target + private - Dir.glob(File.join(@global_state.workspace_path, "**/*.rb")).each do |path| - uri = URI::Generic.from_path(path: path) - # If the document is being managed by the client, then we should use whatever is present in the store instead - # of reading from disk - next if @store.key?(uri) + #: (String name, NodeContext node_context, bool include_declarations) -> void + def handle_constant_references(name, node_context, include_declarations) + declaration = @graph.resolve_constant(name, node_context.nesting) + return unless declaration - parse_result = Prism.parse_lex_file(path) - collect_references(reference_target, parse_result, uri) - rescue Errno::EISDIR, Errno::ENOENT - # If `path` is a directory, just ignore it and continue. If the file doesn't exist, then we also ignore it. + collect_references(declaration.references, [declaration], include_declarations) + end + + #: (String message, NodeContext node_context, bool include_declarations) -> void + def resolve_method_references(message, node_context, include_declarations) + receiver_type = @type_inferrer.infer_receiver_type(node_context) + + declaration = if receiver_type + owner = @graph[receiver_type.name] + owner.find_member("#{message}()") if owner.is_a?(Rubydex::Namespace) end - @store.each do |_uri, document| - collect_references(reference_target, document.parse_result, document.uri) + declarations = if receiver_type.nil? || (receiver_type.is_a?(TypeInferrer::GuessedType) && declaration.nil?) + @graph.search("##{message}()").take(MAX_NUMBER_OF_METHOD_CANDIDATES_WITHOUT_RECEIVER) + elsif declaration + [declaration] + else + [] end + return if declarations.empty? - @locations + collect_references(method_references_for(message), declarations, include_declarations) end - private + # Handles instance and class variable references. Resolves the receiver type from the node context to locate + # the owning namespace, then looks up the member through the ancestor chain via `find_member`. + #: (String name, NodeContext node_context, bool include_declarations) -> void + def handle_variable_references(name, node_context, include_declarations) + type = @type_inferrer.infer_receiver_type(node_context) + return unless type - #: ((Prism::ConstantReadNode | Prism::ConstantPathNode | Prism::ConstantPathTargetNode | Prism::InstanceVariableAndWriteNode | Prism::InstanceVariableOperatorWriteNode | Prism::InstanceVariableOrWriteNode | Prism::InstanceVariableReadNode | Prism::InstanceVariableTargetNode | Prism::InstanceVariableWriteNode | Prism::CallNode | Prism::DefNode) target_node, NodeContext node_context) -> RubyIndexer::ReferenceFinder::Target? - def create_reference_target(target_node, node_context) - case target_node - when Prism::ConstantReadNode, Prism::ConstantPathNode, Prism::ConstantPathTargetNode - name = RubyIndexer::Index.constant_name(target_node) - return unless name - - entries = @global_state.index.resolve(name, node_context.nesting) - return unless entries - - fully_qualified_name = entries.first #: as !nil - .name - RubyIndexer::ReferenceFinder::ConstTarget.new(fully_qualified_name) - when - Prism::InstanceVariableAndWriteNode, - Prism::InstanceVariableOperatorWriteNode, - Prism::InstanceVariableOrWriteNode, - Prism::InstanceVariableReadNode, - Prism::InstanceVariableTargetNode, - Prism::InstanceVariableWriteNode - receiver_type = @global_state.type_inferrer.infer_receiver_type(node_context) - return unless receiver_type - - ancestors = @global_state.index.linearized_ancestors_of(receiver_type.name) - RubyIndexer::ReferenceFinder::InstanceVariableTarget.new(target_node.name.to_s, ancestors) - when Prism::CallNode, Prism::DefNode - RubyIndexer::ReferenceFinder::MethodTarget.new(target_node.name.to_s) - end + owner = @graph[type.name] + return unless owner.is_a?(Rubydex::Namespace) + + declaration = owner.find_member(name) + return unless declaration + + collect_references(declaration.references, [declaration], include_declarations) end - #: (RubyIndexer::ReferenceFinder::Target target, Prism::LexResult parse_result, URI::Generic uri) -> void - def collect_references(target, parse_result, uri) - dispatcher = Prism::Dispatcher.new - finder = RubyIndexer::ReferenceFinder.new( - target, - @global_state.index, - dispatcher, - uri, - include_declarations: @params.dig(:context, :includeDeclaration) || true, - ) - dispatcher.visit(parse_result.value.first) + # Handles global variable references. Globals are keyed by their full name (including `$`) in the graph, so we + # can look them up directly without needing to resolve a receiver type. + #: (String name, bool include_declarations) -> void + def handle_global_variable_references(name, include_declarations) + declaration = @graph[name] + return unless declaration + + collect_references(declaration.references, [declaration], include_declarations) + end - finder.references.each do |reference| - @locations << Interface::Location.new( - uri: uri.to_s, - range: range_from_location(reference.location), - ) + #: (Prism::DefNode target, NodeContext node_context, bool include_declarations) -> void + def handle_def_node_references(target, node_context, include_declarations) + method_name = target.name.to_s + + owner_type = @type_inferrer.infer_receiver_type(node_context) + return unless owner_type + + owner = @graph[owner_type.name] + return unless owner.is_a?(Rubydex::Namespace) + + declaration = owner.find_member("#{method_name}()") + return unless declaration + + collect_references(method_references_for(method_name), [declaration], include_declarations) + end + + # Method references in Rubydex are not yet resolved to specific declarations, so we filter from the global + # method references by name + #: (String) -> Array[Rubydex::MethodReference] + def method_references_for(method_name) + @graph.method_references.select { |reference| reference.name == method_name } + end + + #: (Enumerable[Rubydex::Reference] references, Array[Rubydex::Declaration] declarations, bool include_declarations) -> void + def collect_references(references, declarations, include_declarations) + references.each do |reference| + next if rubydex_internal_uri?(reference.location.uri) + + @locations << reference.to_lsp_location end + + return unless include_declarations + + declarations.each do |declaration| + declaration.definitions.each do |definition| + next if rubydex_internal_uri?(definition.location.uri) + + @locations << definition.to_lsp_selection_location + end + end + end + + #: (String uri) -> bool + def rubydex_internal_uri?(uri) + URI(uri).scheme == "rubydex" + end + + # Write, operator-write, and call-with-message nodes cover more than just the identifier — + # they span the whole assignment or call expression. We only resolve references when the + # cursor is positioned directly on the name itself, not on operators, values, or arguments. + #: (Prism::Location name_loc) -> bool + def cursor_on_name?(name_loc) + start = name_loc.cached_start_code_units_offset(@document.code_units_cache) + finish = name_loc.cached_end_code_units_offset(@document.code_units_cache) + (start...finish).cover?(@char_position) end end end diff --git a/lib/ruby_lsp/rubydex/reference.rb b/lib/ruby_lsp/rubydex/reference.rb index ae47aa0f7..afea73f06 100644 --- a/lib/ruby_lsp/rubydex/reference.rb +++ b/lib/ruby_lsp/rubydex/reference.rb @@ -2,7 +2,7 @@ # frozen_string_literal: true module Rubydex - class ConstantReference + class Reference #: () -> RubyLsp::Interface::Range def to_lsp_range loc = location @@ -12,5 +12,10 @@ def to_lsp_range end: RubyLsp::Interface::Position.new(line: loc.end_line, character: loc.end_column), ) end + + #: () -> RubyLsp::Interface::Location + def to_lsp_location + RubyLsp::Interface::Location.new(uri: location.uri, range: to_lsp_range) + end end end diff --git a/lib/ruby_lsp/type_inferrer.rb b/lib/ruby_lsp/type_inferrer.rb index 10a58f313..495e51ca9 100644 --- a/lib/ruby_lsp/type_inferrer.rb +++ b/lib/ruby_lsp/type_inferrer.rb @@ -19,7 +19,7 @@ def infer_receiver_type(node_context) infer_receiver_for_call_node(node, node_context) when Prism::InstanceVariableReadNode, Prism::InstanceVariableAndWriteNode, Prism::InstanceVariableWriteNode, Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableTargetNode, - Prism::SuperNode, Prism::ForwardingSuperNode + Prism::SuperNode, Prism::ForwardingSuperNode, Prism::DefNode self_receiver_handling(node_context) when Prism::ClassVariableAndWriteNode, Prism::ClassVariableWriteNode, Prism::ClassVariableOperatorWriteNode, Prism::ClassVariableOrWriteNode, Prism::ClassVariableReadNode, Prism::ClassVariableTargetNode diff --git a/sorbet/rbi/gems/rubydex@0.1.0.beta13.rbi b/sorbet/rbi/gems/rubydex@0.1.0.beta13.rbi index 2894daea1..ed29e469c 100644 --- a/sorbet/rbi/gems/rubydex@0.1.0.beta13.rbi +++ b/sorbet/rbi/gems/rubydex@0.1.0.beta13.rbi @@ -585,6 +585,9 @@ class Rubydex::Reference # source://rubydex//lib/rubydex.rb#11 def initialize(_arg0, _arg1); end + sig { returns(Rubydex::Location) } + def location; end + class << self private diff --git a/test/requests/references_test.rb b/test/requests/references_test.rb index 9297db34e..95869ecc3 100644 --- a/test/requests/references_test.rb +++ b/test/requests/references_test.rb @@ -5,37 +5,659 @@ class ReferencesTest < Minitest::Test def test_finds_constant_references - refs = find_references("test/fixtures/rename_me.rb", { line: 0, character: 6 }).map do |ref| - ref.range.start.line + source = <<~RUBY + class Foo + end + + Foo + Foo.new + RUBY + + refs = find_references(source, { line: 3, character: 0 }) + ref_lines = refs.map { |r| r.range.start.line } + assert_includes(ref_lines, 3) + assert_includes(ref_lines, 4) + end + + def test_finds_constant_references_with_include_declaration + source = <<~RUBY + class Foo + end + + Foo + RUBY + + refs = find_references(source, { line: 3, character: 0 }, include_declarations: true) + ref_lines = refs.map { |r| r.range.start.line } + assert_includes(ref_lines, 0) + assert_includes(ref_lines, 3) + end + + def test_finds_method_references_for_call_node + source = <<~RUBY + class Foo + def bar + baz + end + + def baz + end + end + + Foo.new.baz + RUBY + + # Cursor on the `baz` call inside the bar method (line 2, character 4) + refs = find_references(source, { line: 2, character: 4 }) + ref_lines = refs.map { |r| r.range.start.line } + assert_includes(ref_lines, 2) + assert_includes(ref_lines, 9) + end + + def test_finds_method_references_with_include_declaration + source = <<~RUBY + class Foo + def bar + end + end + + Foo.new.bar + RUBY + + # Cursor on the `bar` call (line 5, character 8) + refs = find_references(source, { line: 5, character: 8 }, include_declarations: true) + ref_lines = refs.map { |r| r.range.start.line } + assert_includes(ref_lines, 1) # definition + assert_includes(ref_lines, 5) # reference + end + + def test_does_not_match_simple_names_when_method_receiver_is_known + source = <<~RUBY + class Foo + def self.bar + end + end + + class Qux + def bar + end + end + + it = Qux.new + it.bar + + Foo.bar + RUBY + + # Cursor on `bar` in `Foo.bar` — the receiver is the `Foo` singleton, so only the `Foo.bar` + # declaration is a valid target. We must not surface `Qux#bar` as a candidate declaration. + refs = find_references(source, { line: 13, character: 4 }, include_declarations: true) + ref_lines = refs.map { |r| r.range.start.line } + assert_includes(ref_lines, 1) # def self.bar declaration + assert_includes(ref_lines, 13) # Foo.bar call site + refute_includes(ref_lines, 6) # def bar (Qux) must not appear + + # it.bar must not appear, but we don't currently expose method reference receivers in the API + assert_includes(ref_lines, 11) + + # Cursor on `bar` in `it.bar` — the type of the local `it` can't be inferred, so we fall back + # to including every `bar` declaration and call site as candidates. + refs = find_references(source, { line: 11, character: 3 }, include_declarations: true) + ref_lines = refs.map { |r| r.range.start.line } + assert_includes(ref_lines, 1) # Foo.bar declaration + assert_includes(ref_lines, 6) # Qux#bar declaration + assert_includes(ref_lines, 11) # it.bar call site + assert_includes(ref_lines, 13) # Foo.bar call site + end + + def test_finds_references_from_def_node + source = <<~RUBY + class Foo + def bar + end + end + + Foo.new.bar + RUBY + + # Cursor on the `bar` in `def bar` (line 1, character 6) + refs = find_references(source, { line: 1, character: 6 }) + ref_lines = refs.map { |r| r.range.start.line } + assert_includes(ref_lines, 5) # the Foo.new.bar call + end + + def test_finds_references_from_def_node_with_include_declaration + source = <<~RUBY + class Foo + def bar + end + end + + Foo.new.bar + RUBY + + refs = find_references(source, { line: 1, character: 6 }, include_declarations: true) + ref_lines = refs.map { |r| r.range.start.line } + assert_includes(ref_lines, 1) # definition + assert_includes(ref_lines, 5) # call site + end + + def test_finds_references_for_singleton_method_def + source = <<~RUBY + class Foo + def self.bar + end + end + + Foo.bar + RUBY + + # Cursor on `bar` in `def self.bar` (line 1, character 11) + refs = find_references(source, { line: 1, character: 11 }) + ref_lines = refs.map { |r| r.range.start.line } + assert_includes(ref_lines, 5) # Foo.bar call + end + + def test_singleton_method_def_resolves_to_singleton_declaration_not_instance + source = <<~RUBY + class Foo + def self.bar + end + + def bar + end + end + + Foo.bar + RUBY + + refs = find_references(source, { line: 1, character: 11 }, include_declarations: true) + ref_lines = refs.map { |r| r.range.start.line } + assert_includes(ref_lines, 1) + assert_includes(ref_lines, 8) + refute_includes(ref_lines, 4) + end + + def test_instance_variables_return_empty + source = <<~RUBY + class Foo + def initialize + @name = "test" + end + + def name + @name + end + end + RUBY + + # Placeholder: Rubydex's InstanceVariable#references currently returns an empty array + refs = find_references(source, { line: 2, character: 5 }) + assert_empty(refs) + end + + def test_instance_variable_include_declarations + source = <<~RUBY + class Foo + def initialize + @name = "test" + end + + def name + @name + end + end + RUBY + + # Even though references are empty, the declaration should be included when requested + refs = find_references(source, { line: 2, character: 5 }, include_declarations: true) + ref_lines = refs.map { |r| r.range.start.line } + assert_includes(ref_lines, 2) + end + + def test_class_variables_return_empty + source = <<~RUBY + class Foo + @@count = 0 + + def increment + @@count += 1 + end + + def count + @@count + end + end + RUBY + + # Placeholder: Rubydex's ClassVariable#references currently returns an empty array + refs = find_references(source, { line: 1, character: 2 }) + assert_empty(refs) + end + + def test_class_variable_include_declarations + source = <<~RUBY + class Foo + @@count = 0 + end + RUBY + + refs = find_references(source, { line: 1, character: 2 }, include_declarations: true) + ref_lines = refs.map { |r| r.range.start.line } + assert_includes(ref_lines, 1) + end + + def test_global_variables_return_empty + source = <<~RUBY + $global = "value" + puts $global + RUBY + + # Placeholder: Rubydex's GlobalVariable#references currently returns an empty array + refs = find_references(source, { line: 0, character: 0 }) + assert_empty(refs) + end + + def test_global_variable_include_declarations + source = <<~RUBY + $global = "value" + RUBY + + refs = find_references(source, { line: 0, character: 0 }, include_declarations: true) + ref_lines = refs.map { |r| r.range.start.line } + assert_includes(ref_lines, 0) + end + + def test_finds_constant_path_references + source = <<~RUBY + module Foo + class Bar + end + end + + Foo::Bar + RUBY + + # Cursor on `Bar` in `Foo::Bar` (line 5, character 5) + refs = find_references(source, { line: 5, character: 5 }) + ref_lines = refs.map { |r| r.range.start.line } + assert_includes(ref_lines, 5) + end + + def test_finds_references_for_constant_write_node + source = <<~RUBY + FOO = 1 + puts FOO + RUBY + + refs = find_references(source, { line: 0, character: 0 }) + ref_lines = refs.map { |r| r.range.start.line } + assert_includes(ref_lines, 1) + end + + def test_finds_references_for_constant_and_write_node + source = <<~RUBY + FOO = 1 + FOO &&= 2 + puts FOO + RUBY + + refs = find_references(source, { line: 1, character: 0 }) + ref_lines = refs.map { |r| r.range.start.line } + assert_includes(ref_lines, 1) + assert_includes(ref_lines, 2) + end + + def test_finds_references_for_constant_or_write_node + source = <<~RUBY + FOO ||= 1 + puts FOO + RUBY + + refs = find_references(source, { line: 0, character: 0 }) + ref_lines = refs.map { |r| r.range.start.line } + assert_includes(ref_lines, 1) + end + + def test_finds_references_for_constant_operator_write_node + source = <<~RUBY + FOO = 1 + FOO += 2 + puts FOO + RUBY + + refs = find_references(source, { line: 1, character: 0 }) + ref_lines = refs.map { |r| r.range.start.line } + assert_includes(ref_lines, 1) + assert_includes(ref_lines, 2) + end + + def test_finds_references_for_constant_path_write_node + source = <<~RUBY + module Foo + end + Foo::BAR = 1 + puts Foo::BAR + RUBY + + refs = find_references(source, { line: 2, character: 6 }) + ref_lines = refs.map { |r| r.range.start.line } + assert_includes(ref_lines, 3) + end + + def test_cursor_on_assignment_operator_returns_no_references + source = <<~RUBY + module Foo + end + Foo::BAR = 1 + puts Foo::BAR + RUBY + + refs = find_references(source, { line: 2, character: 9 }) + assert_empty(refs) + end + + def test_finds_references_for_call_operator_write_node + source = <<~RUBY + class Foo + def bar + 0 + end + + def bar=(value) + end + end + + f = Foo.new + f.bar += 1 + f.bar + RUBY + + # Cursor on `bar` in `f.bar += 1` + refs = find_references(source, { line: 10, character: 2 }) + ref_lines = refs.map { |r| r.range.start.line } + assert_includes(ref_lines, 10) + assert_includes(ref_lines, 11) + end + + def test_references_filter_out_rubydex_builtin_uris + source = <<~RUBY + class Object + end + RUBY + + refs = find_references(source, { line: 0, character: 6 }, include_declarations: true) + + refute_empty(refs) + assert(refs.any? { |ref| URI(ref.uri).scheme == "file" }, "Expected at least one file: URI ref") + + refs.each do |ref| + refute_equal("rubydex", URI(ref.uri).scheme, "rubydex: URIs must not leak to the LSP client") end + end - assert_equal([0, 3], refs) + def test_returns_empty_for_no_target + refs = find_references("x = 1", { line: 0, character: 0 }) + assert_empty(refs) + end + + def test_references_in_unsaved_files_are_included + source = <<~RUBY + class MyClass + end + + MyClass + RUBY + + untitled_uri = URI("untitled:Untitled-1") + untitled_source = "MyClass\n" + + refs = find_references(source, { line: 3, character: 0 }) do |graph| + graph.index_source(untitled_uri.to_s, untitled_source, "ruby") + end + + assert_includes(refs.map(&:uri), untitled_uri.to_s) + end + + def test_reference_ranges_are_utf8_code_units_when_negotiated + source = <<~RUBY + class Foo; end + "🙂"; Foo + RUBY + + refs = find_references(source, { line: 0, character: 6 }, encoding: :utf8) + on_line_one = refs.find { |r| r.range.start.line == 1 } #: as !nil + # UTF-8 bytes: " (1) + 🙂 (4) + " (1) + ; (1) + space (1) = 8 before F + assert_equal(8, on_line_one.range.start.character) + assert_equal(11, on_line_one.range.end.character) + end + + def test_reference_ranges_are_utf16_code_units_when_negotiated + source = <<~RUBY + class Foo; end + "🙂"; Foo + RUBY + + refs = find_references(source, { line: 0, character: 6 }, encoding: :utf16) + on_line_one = refs.find { |r| r.range.start.line == 1 } #: as !nil + # UTF-16 code units: " (1) + 🙂 (2) + " (1) + ; (1) + space (1) = 6 before F + assert_equal(6, on_line_one.range.start.character) + assert_equal(9, on_line_one.range.end.character) + end + + def test_reference_ranges_are_utf32_code_units_when_negotiated + source = <<~RUBY + class Foo; end + "🙂"; Foo + RUBY + + refs = find_references(source, { line: 0, character: 6 }, encoding: :utf32) + on_line_one = refs.find { |r| r.range.start.line == 1 } #: as !nil + # UTF-32 code units: " (1) + 🙂 (1) + " (1) + ; (1) + space (1) = 5 before F + assert_equal(5, on_line_one.range.start.character) + assert_equal(8, on_line_one.range.end.character) + end + + def test_unresolved_method_call_surfaces_all_candidate_declarations + source = <<~RUBY + class Foo + def bar + end + end + + class Baz + def bar + end + end + + unknown_var.bar + RUBY + + # Cursor on `bar` in `unknown_var.bar` (line 10, character 12). `unknown_var` can't be + # resolved, so every method named `bar` is a candidate. The user needs to see each one to + # decide which declaration the call actually refers to — we must not drop any candidate + # just because they share an unqualified name. + refs = find_references(source, { line: 10, character: 12 }, include_declarations: true) + ref_lines = refs.map { |r| r.range.start.line } + assert_includes(ref_lines, 1) + assert_includes(ref_lines, 6) + assert_includes(ref_lines, 10) + end + + def test_cursor_on_constant_write_value_returns_no_references + source = <<~RUBY + FOO = 1 + puts FOO + RUBY + + # Cursor on `1` in `FOO = 1` (line 0, character 6). The cursor is not on the constant name, + # so we must return no references. + refs = find_references(source, { line: 0, character: 6 }) + assert_empty(refs) + end + + def test_cursor_on_constant_and_write_operator_returns_no_references + source = <<~RUBY + FOO = 1 + FOO &&= 2 + RUBY + + # Cursor on `&&=` in `FOO &&= 2` (line 1, character 5). Not on the constant name. + refs = find_references(source, { line: 1, character: 5 }) + assert_empty(refs) + end + + def test_cursor_on_constant_operator_write_operator_returns_no_references + source = <<~RUBY + FOO = 1 + FOO += 2 + RUBY + + # Cursor on `+=` in `FOO += 2` (line 1, character 4). Not on the constant name. + refs = find_references(source, { line: 1, character: 4 }) + assert_empty(refs) + end + + def test_cursor_on_instance_variable_write_value_returns_no_references + source = <<~RUBY + class Foo + def initialize + @name = "test" + end + + def name + @name + end + end + RUBY + + # Cursor on `"test"` in `@name = "test"` (line 2, character 12). Not on the variable name. + refs = find_references(source, { line: 2, character: 12 }, include_declarations: true) + assert_empty(refs) + end + + def test_cursor_on_class_variable_write_value_returns_no_references + source = <<~RUBY + class Foo + @@count = 0 + end + RUBY + + # Cursor on `0` in `@@count = 0` (line 1, character 12). Not on the variable name. + refs = find_references(source, { line: 1, character: 12 }, include_declarations: true) + assert_empty(refs) + end + + def test_cursor_on_global_variable_write_value_returns_no_references + source = <<~RUBY + $global = "value" + RUBY + + # Cursor on `"value"` in `$global = "value"` (line 0, character 11). Not on the variable name. + refs = find_references(source, { line: 0, character: 11 }, include_declarations: true) + assert_empty(refs) + end + + def test_cursor_on_call_argument_returns_no_references + source = <<~RUBY + class Foo + def bar(arg) + end + end + + Foo.new.bar(42) + RUBY + + # Cursor on `42` (line 5, character 12). Not on the method name, so no references. + refs = find_references(source, { line: 5, character: 12 }) + assert_empty(refs) + end + + def test_cursor_on_call_operator_write_operator_returns_no_references + source = <<~RUBY + class Foo + def bar + 0 + end + + def bar=(value) + end + end + + f = Foo.new + f.bar += 1 + RUBY + + # Cursor on `+=` in `f.bar += 1` (line 10, character 6). Not on the method name. + refs = find_references(source, { line: 10, character: 6 }) + assert_empty(refs) + end + + def test_cursor_on_def_node_body_returns_no_references + source = <<~RUBY + class Foo + def bar + 42 + end + end + + Foo.new.bar + RUBY + + # Cursor on `42` inside the method body (line 2, character 4). Not on the method name. + refs = find_references(source, { line: 2, character: 4 }) + assert_empty(refs) + end + + def test_does_not_include_declarations_by_default + source = <<~RUBY + class Foo + end + + Foo + RUBY + + refs = find_references(source, { line: 3, character: 0 }) + ref_lines = refs.map { |r| r.range.start.line } + refute_includes(ref_lines, 0) + assert_includes(ref_lines, 3) end private - def find_references(fixture_path, position) - source = File.read(fixture_path) - path = File.expand_path(fixture_path) + #: (String source, Hash[Symbol, Integer] position, ?include_declarations: bool, ?encoding: Symbol) ?{ (Rubydex::Graph) -> void } -> Array[RubyLsp::Interface::Location] + def find_references(source, position, include_declarations: false, encoding: :utf8, &block) + uri = URI::Generic.from_path(path: "/fake/path/test.rb") global_state = RubyLsp::GlobalState.new - global_state.index.index_single(URI::Generic.from_path(path: path), source) + ruby_encoding = case encoding + when :utf8 then Encoding::UTF_8 + when :utf16 then Encoding::UTF_16LE + when :utf32 then Encoding::UTF_32LE + end + global_state.instance_variable_set(:@encoding, ruby_encoding) + graph = global_state.graph + graph.encoding = encoding.to_s + + graph.index_source(uri.to_s, source, "ruby") + block&.call(graph) + graph.resolve store = RubyLsp::Store.new(global_state) document = RubyLsp::RubyDocument.new( source: source, version: 1, - uri: URI::Generic.from_path(path: path), + uri: uri, global_state: global_state, ) - # In addition to glob files from the workspace, we also want to test references collection from the store - store.set(uri: URI::Generic.from_path(path: path), source: source, version: 1, language_id: :ruby) - RubyLsp::Requests::References.new( global_state, store, document, - { position: position }, + { + position: position, + context: { includeDeclaration: include_declarations }, + }, ).perform end end