class RubyLsp::Document
def ==(other)
def ==(other) @source == other.source end
def cache_fetch(request_name, &block)
def cache_fetch(request_name, &block) cached = @cache[request_name] return cached if cached result = block.call(self) @cache[request_name] = result result end
def cache_get(request_name)
def cache_get(request_name) @cache[request_name] end
def cache_set(request_name, value)
def cache_set(request_name, value) @cache[request_name] = value end
def comments
def comments @parse_result.comments end
def create_scanner
def create_scanner Scanner.new(@source, @encoding) end
def initialize(source:, version:, uri:, encoding: Constant::PositionEncodingKind::UTF8)
def initialize(source:, version:, uri:, encoding: Constant::PositionEncodingKind::UTF8) @cache = T.let({}, T::Hash[String, T.untyped]) @encoding = T.let(encoding, String) @source = T.let(source, String) @version = T.let(version, Integer) @uri = T.let(uri, URI::Generic) @needs_parsing = T.let(true, T::Boolean) @parse_result = T.let(parse, Prism::ParseResult) end
def locate(node, char_position, node_types: [])
def locate(node, char_position, node_types: []) queue = T.let(node.child_nodes.compact, T::Array[T.nilable(Prism::Node)]) closest = node parent = T.let(nil, T.nilable(Prism::Node)) nesting = T.let([], T::Array[T.any(Prism::ClassNode, Prism::ModuleNode)]) until queue.empty? candidate = queue.shift # Skip nil child nodes next if candidate.nil? # 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 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_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_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 && 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?(Prism::ClassNode) || candidate.is_a?(Prism::ModuleNode) nesting << candidate end # If there are node types to filter by, and the current node is not one of those types, then skip it next if node_types.any? && node_types.none? { |type| candidate.class == type } # 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_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_path.location.slice }] end
def locate_node(position, node_types: [])
def locate_node(position, node_types: []) locate(@parse_result.value, create_scanner.find_char_position(position), node_types: node_types) end
def parse; end
def parse; end
def push_edits(edits, version:)
def push_edits(edits, version:) edits.each do |edit| range = edit[:range] scanner = create_scanner start_position = scanner.find_char_position(range[:start]) end_position = scanner.find_char_position(range[:end]) @source[start_position...end_position] = edit[:text] end @version = version @needs_parsing = true @cache.clear end
def sorbet_sigil_is_true_or_higher
def sorbet_sigil_is_true_or_higher parse_result.magic_comments.any? do |comment| comment.key == "typed" && ["true", "strict", "strong"].include?(comment.value) end end
def syntax_error?
def syntax_error? @parse_result.failure? end
def tree
def tree @parse_result.value end
def typechecker_enabled?
def typechecker_enabled? DependencyDetector.instance.typechecker && sorbet_sigil_is_true_or_higher end