diff --git a/Gemfile.lock b/Gemfile.lock index 4868a93328..00641de75f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -61,6 +61,8 @@ GEM nokogiri (1.15.2) mini_portile2 (~> 2.8.2) racc (~> 1.4) + nokogiri (1.15.2-arm64-darwin) + racc (~> 1.4) parallel (1.23.0) parser (3.2.2.3) ast (~> 2.4.1) diff --git a/lib/ruby_lsp/document.rb b/lib/ruby_lsp/document.rb index 064f0b3311..eb26e7448c 100644 --- a/lib/ruby_lsp/document.rb +++ b/lib/ruby_lsp/document.rb @@ -9,8 +9,8 @@ class Document RangeShape = T.type_alias { { start: PositionShape, end: PositionShape } } EditShape = T.type_alias { { range: RangeShape, text: String } } - sig { returns(T.nilable(SyntaxTree::Node)) } - attr_reader :tree + sig { returns(YARP::ParseResult) } + attr_reader :parse_result sig { returns(String) } attr_reader :source @@ -29,10 +29,12 @@ def initialize(source:, version:, uri:, encoding: Constant::PositionEncodingKind @version = T.let(version, Integer) @uri = T.let(uri, URI::Generic) @unparsed_edits = T.let([], T::Array[EditShape]) - @syntax_error = T.let(false, T::Boolean) - @tree = T.let(SyntaxTree.parse(@source), T.nilable(SyntaxTree::Node)) - rescue SyntaxTree::Parser::ParseError - @syntax_error = true + @parse_result = T.let(YARP.parse(@source), YARP::ParseResult) + end + + sig { returns(YARP::Node) } + def tree + @parse_result.value end sig { params(other: Document).returns(T::Boolean) } @@ -89,20 +91,17 @@ def parse return if @unparsed_edits.empty? @unparsed_edits.clear - @tree = SyntaxTree.parse(@source) - @syntax_error = false - rescue SyntaxTree::Parser::ParseError - @syntax_error = true + @parse_result = YARP.parse(@source) end sig { returns(T::Boolean) } def syntax_error? - @syntax_error + @parse_result.failure? end sig { returns(T::Boolean) } def parsed? - !@tree.nil? + !@parse_result.value.nil? end sig { returns(Scanner) } @@ -113,27 +112,25 @@ def create_scanner sig do params( position: PositionShape, - node_types: T::Array[T.class_of(SyntaxTree::Node)], - ).returns([T.nilable(SyntaxTree::Node), T.nilable(SyntaxTree::Node), T::Array[String]]) + node_types: T::Array[T.class_of(YARP::Node)], + ).returns([T.nilable(YARP::Node), T.nilable(YARP::Node), T::Array[String]]) end def locate_node(position, node_types: []) - return [nil, nil, []] unless parsed? - - locate(T.must(@tree), create_scanner.find_char_position(position), node_types: node_types) + locate(@parse_result.value, create_scanner.find_char_position(position), node_types: node_types) end sig do params( - node: SyntaxTree::Node, + node: YARP::Node, char_position: Integer, - node_types: T::Array[T.class_of(SyntaxTree::Node)], - ).returns([T.nilable(SyntaxTree::Node), T.nilable(SyntaxTree::Node), T::Array[String]]) + node_types: T::Array[T.class_of(YARP::Node)], + ).returns([T.nilable(YARP::Node), T.nilable(YARP::Node), T::Array[String]]) end def locate(node, char_position, node_types: []) - queue = T.let(node.child_nodes.compact, T::Array[T.nilable(SyntaxTree::Node)]) + queue = T.let(node.child_nodes.compact, T::Array[T.nilable(YARP::Node)]) closest = node - parent = T.let(nil, T.nilable(SyntaxTree::Node)) - nesting = T.let([], T::Array[T.any(SyntaxTree::ClassDeclaration, SyntaxTree::ModuleDeclaration)]) + parent = T.let(nil, T.nilable(YARP::Node)) + nesting = T.let([], T::Array[T.any(YARP::ClassNode, YARP::ModuleNode)]) until queue.empty? candidate = queue.shift @@ -144,24 +141,24 @@ def locate(node, char_position, node_types: []) # Add the next child_nodes to the queue to be processed. The order here is important! We want to move in the # same order as the visiting mechanism, which means searching the child nodes before moving on to the next # sibling - queue.unshift(*candidate.child_nodes) + T.unsafe(queue).unshift(*candidate.child_nodes) # Skip if the current node doesn't cover the desired position loc = candidate.location - next unless (loc.start_char...loc.end_char).cover?(char_position) + next unless (loc.start_offset...loc.end_offset).cover?(char_position) # If the node's start character is already past the position, then we should've found the closest node # already - break if char_position < loc.start_char + break if char_position < loc.start_offset # If the candidate starts after the end of the previous nesting level, then we've exited that nesting level and # need to pop the stack previous_level = nesting.last - nesting.pop if previous_level && candidate.start_char > previous_level.end_char + nesting.pop if previous_level && loc.start_offset > previous_level.location.end_offset # Keep track of the nesting where we found the target. This is used to determine the fully qualified name of the # target when it is a constant - if candidate.is_a?(SyntaxTree::ClassDeclaration) || candidate.is_a?(SyntaxTree::ModuleDeclaration) + if candidate.is_a?(YARP::ClassNode) || candidate.is_a?(YARP::ModuleNode) nesting << candidate end @@ -170,13 +167,13 @@ def locate(node, char_position, node_types: []) # If the current node is narrower than or equal to the previous closest node, then it is more precise closest_loc = closest.location - if loc.end_char - loc.start_char <= closest_loc.end_char - closest_loc.start_char + if loc.end_offset - loc.start_offset <= closest_loc.end_offset - closest_loc.start_offset parent = closest closest = candidate end end - [closest, parent, nesting.map { |n| n.constant.constant.value }] + [closest, parent, nesting.map { |n| n.constant_path.location.slice }] end class Scanner diff --git a/lib/ruby_lsp/event_emitter.rb b/lib/ruby_lsp/event_emitter.rb index 2b846159c4..de61144767 100644 --- a/lib/ruby_lsp/event_emitter.rb +++ b/lib/ruby_lsp/event_emitter.rb @@ -52,7 +52,7 @@ def emit_for_target(node) # Visit dispatchers are below. Notice that for nodes that create a new scope (e.g.: classes, modules, method defs) # we need both an `on_*` and `after_*` event. This is because some requests must know when we exit the scope - sig { override.params(node: T.nilable(SyntaxTree::Node)).void } + sig { override.params(node: T.nilable(YARP::Node)).void } def visit(node) @listeners[:on_node]&.each { |l| T.unsafe(l).on_node(node) } super diff --git a/lib/ruby_lsp/requests/document_highlight.rb b/lib/ruby_lsp/requests/document_highlight.rb index bb0f1dd27c..13dc8a281a 100644 --- a/lib/ruby_lsp/requests/document_highlight.rb +++ b/lib/ruby_lsp/requests/document_highlight.rb @@ -32,8 +32,8 @@ class DocumentHighlight < Listener sig do params( - target: T.nilable(SyntaxTree::Node), - parent: T.nilable(SyntaxTree::Node), + target: T.nilable(YARP::Node), + parent: T.nilable(YARP::Node), emitter: EventEmitter, message_queue: Thread::Queue, ).void @@ -49,9 +49,12 @@ def initialize(target, parent, emitter, message_queue) case target when *DIRECT_HIGHLIGHTS Support::HighlightTarget.new(target) - when SyntaxTree::Ident - relevant_node = parent.is_a?(SyntaxTree::Params) ? target : parent - Support::HighlightTarget.new(relevant_node) + when YARP::GlobalVariableWriteNode # , InstanceVariableWriteNode, ConstantWriteNode, ClassVariableWriteNode + Support::HighlightTarget.new(target) + + # when SyntaxTree::Ident + # relevant_node = parent.is_a?(YARP::ParametersNode) ? target : parent + # Support::HighlightTarget.new(relevant_node) end @target = T.let(highlight_target, T.nilable(Support::HighlightTarget)) @@ -59,7 +62,7 @@ def initialize(target, parent, emitter, message_queue) emitter.register(self, :on_node) if @target end - sig { params(node: T.nilable(SyntaxTree::Node)).void } + sig { params(node: T.nilable(YARP::Node)).void } def on_node(node) return if node.nil? @@ -71,13 +74,13 @@ def on_node(node) DIRECT_HIGHLIGHTS = T.let( [ - SyntaxTree::GVar, - SyntaxTree::IVar, - SyntaxTree::Const, - SyntaxTree::CVar, - SyntaxTree::VarField, + YARP::GlobalVariableReadNode, + YARP::InstanceVariableReadNode, + YARP::ConstantReadNode, + YARP::ClassVariableReadNode, + # YARP::VarField, ], - T::Array[T.class_of(SyntaxTree::Node)], + T::Array[T.class_of(YARP::Node)], ) sig { params(match: Support::HighlightTarget::HighlightMatch).void } diff --git a/lib/ruby_lsp/requests/support/highlight_target.rb b/lib/ruby_lsp/requests/support/highlight_target.rb index 47895307e3..1fd6b87e6b 100644 --- a/lib/ruby_lsp/requests/support/highlight_target.rb +++ b/lib/ruby_lsp/requests/support/highlight_target.rb @@ -16,56 +16,56 @@ class HighlightMatch sig { returns(Integer) } attr_reader :type - sig { returns(SyntaxTree::Node) } + sig { returns(YARP::Node) } attr_reader :node - sig { params(type: Integer, node: SyntaxTree::Node).void } + sig { params(type: Integer, node: YARP::Node).void } def initialize(type:, node:) @type = type @node = node end end - sig { params(node: SyntaxTree::Node).void } + sig { params(node: YARP::Node).void } def initialize(node) @node = node @value = T.let(value(node), T.nilable(String)) end - sig { params(other: SyntaxTree::Node).returns(T.nilable(HighlightMatch)) } + sig { params(other: YARP::Node).returns(T.nilable(HighlightMatch)) } def highlight_type(other) - matched_highlight(other) if other.is_a?(SyntaxTree::Params) || (@value && @value == value(other)) + matched_highlight(other) if other.is_a?(YARP::ParametersNode) || (@value && @value == value(other)) end private # Match the target type (where the cursor is positioned) with the `other` type (the node we're currently # visiting) - sig { params(other: SyntaxTree::Node).returns(T.nilable(HighlightMatch)) } + sig { params(other: YARP::Node).returns(T.nilable(HighlightMatch)) } def matched_highlight(other) case @node # Method definitions and invocations - when SyntaxTree::VCall, SyntaxTree::CallNode, SyntaxTree::Command, - SyntaxTree::CommandCall, SyntaxTree::DefNode + when YARP::CallNode, YARP::DefNode case other - when SyntaxTree::VCall, SyntaxTree::CallNode, SyntaxTree::Command, SyntaxTree::CommandCall + when YARP::CallNode HighlightMatch.new(type: READ, node: other) - when SyntaxTree::DefNode + when YARP::DefNode HighlightMatch.new(type: WRITE, node: other.name) end # Variables, parameters and constants - when SyntaxTree::GVar, SyntaxTree::IVar, SyntaxTree::Const, SyntaxTree::CVar, SyntaxTree::VarField, - SyntaxTree::VarRef, SyntaxTree::Ident + when YARP::GlobalVariableReadNode, YARP::InstanceVariableReadNode, YARP::ConstantReadNode, YARP::ClassVariableReadNode, SyntaxTree::VarField, + # SyntaxTree::VarRef, SyntaxTree::Ident, + YARP::GlobalVariableReadNode case other - when SyntaxTree::VarField - HighlightMatch.new(type: WRITE, node: other) - when SyntaxTree::VarRef - HighlightMatch.new(type: READ, node: other) - when SyntaxTree::ClassDeclaration, SyntaxTree::ModuleDeclaration - HighlightMatch.new(type: WRITE, node: other.constant) - when SyntaxTree::ConstPathRef - HighlightMatch.new(type: READ, node: other.constant) - when SyntaxTree::Params + # when SyntaxTree::VarField + # HighlightMatch.new(type: WRITE, node: other) + # when SyntaxTree::VarRef + # HighlightMatch.new(type: READ, node: other) + when YARP::ClassNode, YARP::ModuleNode + HighlightMatch.new(type: WRITE, node: other.location) + when YARP::ConstantPathNode + HighlightMatch.new(type: READ, node: other.location) + when YARP::ParametersNode params = other.child_nodes.compact match = params.find { |param| value(param) == @value } HighlightMatch.new(type: WRITE, node: match) if match @@ -73,24 +73,23 @@ def matched_highlight(other) end end - sig { params(node: SyntaxTree::Node).returns(T.nilable(String)) } + sig { params(node: YARP::Node).returns(T.nilable(String)) } def value(node) case node - when SyntaxTree::ConstPathRef, SyntaxTree::ConstPathField, SyntaxTree::TopConstField - node.constant.value - when SyntaxTree::GVar, SyntaxTree::IVar, SyntaxTree::Const, SyntaxTree::CVar, SyntaxTree::Ident - node.value - when SyntaxTree::Field, SyntaxTree::DefNode, SyntaxTree::RestParam, - SyntaxTree::KwRestParam, SyntaxTree::BlockArg + when YARP::ConstantPathNode, YARP::ConstantPathNode # , SyntaxTree::TopConstField + node.location.slice + when YARP::GlobalVariableReadNode, YARP::InstanceVariableReadNode, YARP::ConstantReadNode, YARP::ClassVariableReadNode # , SyntaxTree::VarField, + node.location.slice + when YARP::DefNode, YARP::RestParameterNode, + YARP::KeywordRestParameterNode, YARP::BlockArgumentNode node.name&.value - when SyntaxTree::VarField, SyntaxTree::VarRef, SyntaxTree::VCall - value = node.value - value.value unless value.nil? || value.is_a?(Symbol) - when SyntaxTree::CallNode, SyntaxTree::Command, SyntaxTree::CommandCall - message = node.message - message.value unless message.is_a?(Symbol) - when SyntaxTree::ClassDeclaration, SyntaxTree::ModuleDeclaration - node.constant.constant.value + # when SyntaxTree::VarField, SyntaxTree::VarRef, SyntaxTree::VCall + # value = node.value + # value.value unless value.nil? || value.is_a?(Symbol) + when YARP::CallNode + node.message + when YARP::ClassNode, YARP::ModuleNode, + node.location.slice end end end diff --git a/sorbet/rbi/shims/yarp.rbi b/sorbet/rbi/shims/yarp.rbi new file mode 100644 index 0000000000..e26ab34a96 --- /dev/null +++ b/sorbet/rbi/shims/yarp.rbi @@ -0,0 +1,60 @@ +# typed: true + +module YARP + class << self + sig { params(source: String).returns(ParseResult) } + def parse(*source); end + end + + class ParseResult + sig { returns(YARP::Node) } + def value; end + + sig { returns(T::Boolean) } + def failure?; end + + sig { returns(T::Boolean) } + def success?; end + end + + class Node + sig { returns(T::Array[T.nilable(YARP::Node)]) } + def child_nodes; end + + sig { returns(Location) } + def location; end + end + + class Location + sig { returns(Integer) } + def start_offset; end + + sig { returns(Integer) } + def end_offset; end + + sig { returns(Integer) } + def start_line; end + + sig { returns(Integer) } + def end_line; end + + sig { returns(Integer) } + def start_column; end + + sig { returns(Integer) } + def end_column; end + + sig { returns(String) } + def slice; end + end + + class ClassNode + sig { returns(ConstantPathNode) } + def constant_path; end + end + + class ModuleNode + sig { returns(ConstantPathNode) } + def constant_path; end + end +end diff --git a/test/document_test.rb b/test/document_test.rb index efef6cfe00..94fa3cd78e 100644 --- a/test/document_test.rb +++ b/test/document_test.rb @@ -328,12 +328,12 @@ def test_parsed_returns_true_when_parsed_successfully assert_predicate(document, :parsed?) end - def test_parsed_returns_false_when_parsing_fails + def test_parsed_returns_true_when_parsing_fails document = RubyLsp::Document.new(source: +<<~RUBY, version: 1, uri: URI("file:///foo.rb")) class Foo RUBY - refute_predicate(document, :parsed?) + assert_predicate(document, :parsed?) end def test_document_handle_4_byte_unicode_characters @@ -447,33 +447,33 @@ class Post < ActiveRecord::Base # Locate the `ActiveRecord` module found, parent = document.locate_node({ line: 0, character: 19 }) - assert_instance_of(SyntaxTree::Const, found) - assert_equal("ActiveRecord", T.cast(found, SyntaxTree::Const).value) + assert_instance_of(YARP::ConstantReadNode, found) + assert_equal("ActiveRecord", T.cast(found, YARP::ConstantReadNode).location.slice) - assert_instance_of(SyntaxTree::VarRef, parent) - assert_equal("ActiveRecord", T.cast(parent, SyntaxTree::VarRef).value.value) + assert_instance_of(YARP::ConstantPathNode, parent) + assert_equal("ActiveRecord", T.cast(parent, YARP::ConstantPathNode).child_nodes.first.location.slice) # Locate the `Base` class found, parent = T.cast( document.locate_node({ line: 0, character: 27 }), - [SyntaxTree::Const, SyntaxTree::ConstPathRef, T::Array[String]], + [YARP::ConstantReadNode, YARP::ConstantPathNode, T::Array[String]], ) - assert_instance_of(SyntaxTree::Const, found) - assert_equal("Base", found.value) + assert_instance_of(YARP::ConstantReadNode, found) + assert_equal("Base", found.location.slice) - assert_instance_of(SyntaxTree::ConstPathRef, parent) - assert_equal("Base", parent.constant.value) - assert_equal("ActiveRecord", T.cast(parent.parent, SyntaxTree::VarRef).value.value) + assert_instance_of(YARP::ConstantPathNode, parent) + assert_equal("Base", parent.child_nodes[1].location.slice) + assert_equal("ActiveRecord", parent.child_nodes[0].location.slice) # Locate the `where` invocation found, parent = T.cast( document.locate_node({ line: 3, character: 4 }), - [SyntaxTree::Ident, SyntaxTree::CallNode, T::Array[String]], + [YARP::CallNode, YARP::StatementsNode, T::Array[String]], ) - assert_instance_of(SyntaxTree::Ident, found) - assert_equal("where", found.value) + assert_instance_of(YARP::CallNode, found) + assert_equal("where", found.message_loc.slice) - assert_instance_of(SyntaxTree::CallNode, parent) + assert_instance_of(YARP::StatementsNode, parent) end def test_locate_returns_nesting @@ -494,11 +494,11 @@ def baz RUBY found, _parent, nesting = document.locate_node({ line: 9, character: 6 }) - assert_equal("Qux", T.cast(found, SyntaxTree::Const).value) + assert_equal("Qux", T.cast(found, YARP::ConstantReadNode).location.slice) assert_equal(["Foo", "Bar"], nesting) found, _parent, nesting = document.locate_node({ line: 3, character: 6 }) - assert_equal("Hello", T.cast(found, SyntaxTree::Const).value) + assert_equal("Hello", T.cast(found, YARP::ConstantReadNode).location.slice) assert_equal(["Foo", "Other"], nesting) end @@ -513,8 +513,8 @@ def baz end RUBY - found, _parent, nesting = document.locate_node({ line: 3, character: 6 }, node_types: [SyntaxTree::Const]) - assert_equal("Qux", T.cast(found, SyntaxTree::Const).value) + found, _parent, nesting = document.locate_node({ line: 3, character: 6 }, node_types: [YARP::ConstantReadNode]) + assert_equal("Qux", T.cast(found, YARP::ConstantReadNode).location.slice) assert_equal(["Foo", "Bar"], nesting) end