diff --git a/Gemfile.lock b/Gemfile.lock index 442089c75c..3222442309 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,7 +5,7 @@ PATH language_server-protocol (~> 3.17.0) sorbet-runtime syntax_tree (>= 6.1.1, < 7) - yarp (>= 0.11, < 0.13) + yarp (>= 0.12, < 0.13) GEM remote: https://rubygems.org/ diff --git a/lib/ruby_lsp/document.rb b/lib/ruby_lsp/document.rb index 55d86b5a7d..4bd9e91208 100644 --- a/lib/ruby_lsp/document.rb +++ b/lib/ruby_lsp/document.rb @@ -37,6 +37,11 @@ def tree @parse_result.value end + sig { returns(T::Array[YARP::Comment]) } + def comments + @parse_result.comments + end + sig { params(other: Document).returns(T::Boolean) } def ==(other) @source == other.source diff --git a/lib/ruby_lsp/executor.rb b/lib/ruby_lsp/executor.rb index 8e1c193e8c..81370072c5 100644 --- a/lib/ruby_lsp/executor.rb +++ b/lib/ruby_lsp/executor.rb @@ -97,7 +97,7 @@ def run(request) # Run listeners for the document emitter = EventEmitter.new document_symbol = Requests::DocumentSymbol.new(emitter, @message_queue) - document_link = Requests::DocumentLink.new(uri, emitter, @message_queue) + document_link = Requests::DocumentLink.new(uri, document.comments, emitter, @message_queue) code_lens = Requests::CodeLens.new(uri, emitter, @message_queue, @test_library) semantic_highlighting = Requests::SemanticHighlighting.new(emitter, @message_queue) diff --git a/lib/ruby_lsp/requests/document_link.rb b/lib/ruby_lsp/requests/document_link.rb index 211b2c0241..c75b16ae60 100644 --- a/lib/ruby_lsp/requests/document_link.rb +++ b/lib/ruby_lsp/requests/document_link.rb @@ -75,8 +75,15 @@ def gem_paths sig { override.returns(ResponseType) } attr_reader :_response - sig { params(uri: URI::Generic, emitter: EventEmitter, message_queue: Thread::Queue).void } - def initialize(uri, emitter, message_queue) + sig do + params( + uri: URI::Generic, + comments: T::Array[YARP::Comment], + emitter: EventEmitter, + message_queue: Thread::Queue, + ).void + end + def initialize(uri, comments, emitter, message_queue) super(emitter, message_queue) # Match the version based on the version in the RBI file name. Notice that the `@` symbol is sanitized to `%40` @@ -85,13 +92,49 @@ def initialize(uri, emitter, message_queue) version_match = path ? /(?<=%40)[\d.]+(?=\.rbi$)/.match(path) : nil @gem_version = T.let(version_match && version_match[0], T.nilable(String)) @_response = T.let([], T::Array[Interface::DocumentLink]) + @lines_to_comments = T.let( + comments.to_h do |comment| + [comment.location.end_line, comment] + end, + T::Hash[Integer, YARP::Comment], + ) - emitter.register(self, :on_comment) + emitter.register(self, :on_def, :on_class, :on_module, :on_constant_write, :on_constant_path_write) end - sig { params(node: SyntaxTree::Comment).void } - def on_comment(node) - match = node.value.match(%r{source://.*#\d+$}) + sig { params(node: YARP::DefNode).void } + def on_def(node) + extract_document_link(node) + end + + sig { params(node: YARP::ClassNode).void } + def on_class(node) + extract_document_link(node) + end + + sig { params(node: YARP::ModuleNode).void } + def on_module(node) + extract_document_link(node) + end + + sig { params(node: YARP::ConstantWriteNode).void } + def on_constant_write(node) + extract_document_link(node) + end + + sig { params(node: YARP::ConstantPathWriteNode).void } + def on_constant_path_write(node) + extract_document_link(node) + end + + private + + sig { params(node: YARP::Node).void } + def extract_document_link(node) + comment = @lines_to_comments[node.location.start_line - 1] + return unless comment + + match = comment.location.slice.match(%r{source://.*#\d+$}) return unless match uri = T.cast(URI(T.must(match[0])), URI::Source) @@ -102,14 +145,12 @@ def on_comment(node) return if file_path.nil? @_response << Interface::DocumentLink.new( - range: range_from_node(node), + range: range_from_location(comment.location), target: "file://#{file_path}##{uri.line_number}", tooltip: "Jump to #{file_path}##{uri.line_number}", ) end - private - # Try to figure out the gem version for a source:// link. The order of precedence is: # 1. The version in the URI # 2. The version in the RBI file name diff --git a/ruby-lsp.gemspec b/ruby-lsp.gemspec index 533fae8e19..35eb8065b7 100644 --- a/ruby-lsp.gemspec +++ b/ruby-lsp.gemspec @@ -20,7 +20,7 @@ Gem::Specification.new do |s| s.add_dependency("language_server-protocol", "~> 3.17.0") s.add_dependency("sorbet-runtime") s.add_dependency("syntax_tree", ">= 6.1.1", "< 7") - s.add_dependency("yarp", ">= 0.11", "< 0.13") + s.add_dependency("yarp", ">= 0.12", "< 0.13") s.required_ruby_version = ">= 3.0" end diff --git a/test/expectations/document_link/source_comment.exp.json b/test/expectations/document_link/source_comment.exp.json index 5196d89375..a33e6aba58 100644 --- a/test/expectations/document_link/source_comment.exp.json +++ b/test/expectations/document_link/source_comment.exp.json @@ -41,6 +41,76 @@ }, "target": "file://BUNDLER_PATH/gems/syntax_tree-SYNTAX_TREE_VERSION/lib/syntax_tree.rb#39", "tooltip": "Jump to BUNDLER_PATH/gems/syntax_tree-SYNTAX_TREE_VERSION/lib/syntax_tree.rb#39" + }, + { + "range": { + "start": { + "line": 12, + "character": 0 + }, + "end": { + "line": 12, + "character": 44 + } + }, + "target": "file://BUNDLER_PATH/gems/syntax_tree-SYNTAX_TREE_VERSION/lib/syntax_tree.rb#1", + "tooltip": "Jump to BUNDLER_PATH/gems/syntax_tree-SYNTAX_TREE_VERSION/lib/syntax_tree.rb#1" + }, + { + "range": { + "start": { + "line": 16, + "character": 0 + }, + "end": { + "line": 16, + "character": 44 + } + }, + "target": "file://BUNDLER_PATH/gems/syntax_tree-SYNTAX_TREE_VERSION/lib/syntax_tree.rb#2", + "tooltip": "Jump to BUNDLER_PATH/gems/syntax_tree-SYNTAX_TREE_VERSION/lib/syntax_tree.rb#2" + }, + { + "range": { + "start": { + "line": 20, + "character": 0 + }, + "end": { + "line": 20, + "character": 44 + } + }, + "target": "file://BUNDLER_PATH/gems/syntax_tree-SYNTAX_TREE_VERSION/lib/syntax_tree.rb#3", + "tooltip": "Jump to BUNDLER_PATH/gems/syntax_tree-SYNTAX_TREE_VERSION/lib/syntax_tree.rb#3" + }, + { + "range": { + "start": { + "line": 24, + "character": 0 + }, + "end": { + "line": 24, + "character": 44 + } + }, + "target": "file://BUNDLER_PATH/gems/syntax_tree-SYNTAX_TREE_VERSION/lib/syntax_tree.rb#4", + "tooltip": "Jump to BUNDLER_PATH/gems/syntax_tree-SYNTAX_TREE_VERSION/lib/syntax_tree.rb#4" + }, + { + "range": { + "start": { + "line": 27, + "character": 0 + }, + "end": { + "line": 27, + "character": 44 + } + }, + "target": "file://BUNDLER_PATH/gems/syntax_tree-SYNTAX_TREE_VERSION/lib/syntax_tree.rb#5", + "tooltip": "Jump to BUNDLER_PATH/gems/syntax_tree-SYNTAX_TREE_VERSION/lib/syntax_tree.rb#5" } ] } diff --git a/test/expectations/expectations_test_runner.rb b/test/expectations/expectations_test_runner.rb index f6f71d89ea..c05b9550d0 100644 --- a/test/expectations/expectations_test_runner.rb +++ b/test/expectations/expectations_test_runner.rb @@ -50,9 +50,6 @@ def default_args RB Dir.glob(TEST_FIXTURES_GLOB).each do |path| - # temporarily skip until we figure out comment handling - next if handler_class == RubyLsp::Requests::DocumentLink && path == "test/fixtures/source_comment.rb" - test_name = File.basename(path, ".rb") expectations_dir = File.join(TEST_EXP_DIR, expectation_suffix) diff --git a/test/fixtures/source_comment.rb b/test/fixtures/source_comment.rb index e8b062a7c6..170425b208 100644 --- a/test/fixtures/source_comment.rb +++ b/test/fixtures/source_comment.rb @@ -10,6 +10,24 @@ def bar def baz end +# source://syntax_tree//lib/syntax_tree.rb#1 +class Foo +end + +# source://syntax_tree//lib/syntax_tree.rb#2 +class Foo::Bar +end + +# source://syntax_tree//lib/syntax_tree.rb#3 +module Foo +end + +# source://syntax_tree//lib/syntax_tree.rb#4 +FOO = 1 + +# source://syntax_tree//lib/syntax_tree.rb#5 +FOO::BAR = 1 + # source://deleted//lib/foo.rb.rb#1 def baz end diff --git a/test/integration_test.rb b/test/integration_test.rb index 9d0e8993b2..4487f7309d 100644 --- a/test/integration_test.rb +++ b/test/integration_test.rb @@ -149,7 +149,6 @@ def test_semantic_highlighting end def test_document_link - skip initialize_lsp(["documentLink"]) open_file_with(<<~DOC) # source://syntax_tree/#{Gem::Specification.find_by_name("syntax_tree").version}/lib/syntax_tree.rb#39 diff --git a/test/requests/document_link_expectations_test.rb b/test/requests/document_link_expectations_test.rb index 5b1ab655f0..0309325f55 100644 --- a/test/requests/document_link_expectations_test.rb +++ b/test/requests/document_link_expectations_test.rb @@ -26,7 +26,7 @@ def run_expectations(source) document = RubyLsp::Document.new(source: source, version: 1, uri: uri) emitter = RubyLsp::EventEmitter.new - listener = RubyLsp::Requests::DocumentLink.new(uri, emitter, message_queue) + listener = RubyLsp::Requests::DocumentLink.new(uri, document.comments, emitter, message_queue) emitter.visit(document.tree) listener.response ensure