Skip to content

Commit af60237

Browse files
authored
Make document generic (#2436)
* Make Document generic * Move locate to RubyDocument and ERBDocument
1 parent e18d456 commit af60237

23 files changed

+253
-147
lines changed

lib/ruby_lsp/document.rb

+8-114
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@ class LanguageId < T::Enum
1212

1313
extend T::Sig
1414
extend T::Helpers
15+
extend T::Generic
16+
17+
ParseResultType = type_member
1518

1619
abstract!
1720

18-
sig { returns(Prism::ParseResult) }
21+
sig { returns(ParseResultType) }
1922
attr_reader :parse_result
2023

2124
sig { returns(String) }
@@ -38,10 +41,10 @@ def initialize(source:, version:, uri:, encoding: Encoding::UTF_8)
3841
@version = T.let(version, Integer)
3942
@uri = T.let(uri, URI::Generic)
4043
@needs_parsing = T.let(true, T::Boolean)
41-
@parse_result = T.let(parse, Prism::ParseResult)
44+
@parse_result = T.let(parse, ParseResultType)
4245
end
4346

44-
sig { params(other: Document).returns(T::Boolean) }
47+
sig { params(other: Document[T.untyped]).returns(T::Boolean) }
4548
def ==(other)
4649
self.class == other.class && uri == other.uri && @source == other.source
4750
end
@@ -54,7 +57,7 @@ def language_id; end
5457
type_parameters(:T)
5558
.params(
5659
request_name: String,
57-
block: T.proc.params(document: Document).returns(T.type_parameter(:T)),
60+
block: T.proc.params(document: Document[ParseResultType]).returns(T.type_parameter(:T)),
5861
).returns(T.type_parameter(:T))
5962
end
6063
def cache_fetch(request_name, &block)
@@ -93,7 +96,7 @@ def push_edits(edits, version:)
9396
@cache.clear
9497
end
9598

96-
sig { abstract.returns(Prism::ParseResult) }
99+
sig { abstract.returns(ParseResultType) }
97100
def parse; end
98101

99102
sig { abstract.returns(T::Boolean) }
@@ -104,115 +107,6 @@ def create_scanner
104107
Scanner.new(@source, @encoding)
105108
end
106109

107-
sig do
108-
params(
109-
position: T::Hash[Symbol, T.untyped],
110-
node_types: T::Array[T.class_of(Prism::Node)],
111-
).returns(NodeContext)
112-
end
113-
def locate_node(position, node_types: [])
114-
locate(@parse_result.value, create_scanner.find_char_position(position), node_types: node_types)
115-
end
116-
117-
sig do
118-
params(
119-
node: Prism::Node,
120-
char_position: Integer,
121-
node_types: T::Array[T.class_of(Prism::Node)],
122-
).returns(NodeContext)
123-
end
124-
def locate(node, char_position, node_types: [])
125-
queue = T.let(node.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
126-
closest = node
127-
parent = T.let(nil, T.nilable(Prism::Node))
128-
nesting_nodes = T.let(
129-
[],
130-
T::Array[T.any(
131-
Prism::ClassNode,
132-
Prism::ModuleNode,
133-
Prism::SingletonClassNode,
134-
Prism::DefNode,
135-
Prism::BlockNode,
136-
Prism::LambdaNode,
137-
Prism::ProgramNode,
138-
)],
139-
)
140-
141-
nesting_nodes << node if node.is_a?(Prism::ProgramNode)
142-
call_node = T.let(nil, T.nilable(Prism::CallNode))
143-
144-
until queue.empty?
145-
candidate = queue.shift
146-
147-
# Skip nil child nodes
148-
next if candidate.nil?
149-
150-
# Add the next child_nodes to the queue to be processed. The order here is important! We want to move in the
151-
# same order as the visiting mechanism, which means searching the child nodes before moving on to the next
152-
# sibling
153-
T.unsafe(queue).unshift(*candidate.child_nodes)
154-
155-
# Skip if the current node doesn't cover the desired position
156-
loc = candidate.location
157-
next unless (loc.start_offset...loc.end_offset).cover?(char_position)
158-
159-
# If the node's start character is already past the position, then we should've found the closest node
160-
# already
161-
break if char_position < loc.start_offset
162-
163-
# If the candidate starts after the end of the previous nesting level, then we've exited that nesting level and
164-
# need to pop the stack
165-
previous_level = nesting_nodes.last
166-
nesting_nodes.pop if previous_level && loc.start_offset > previous_level.location.end_offset
167-
168-
# Keep track of the nesting where we found the target. This is used to determine the fully qualified name of the
169-
# target when it is a constant
170-
case candidate
171-
when Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefNode, Prism::BlockNode,
172-
Prism::LambdaNode
173-
nesting_nodes << candidate
174-
end
175-
176-
if candidate.is_a?(Prism::CallNode)
177-
arg_loc = candidate.arguments&.location
178-
blk_loc = candidate.block&.location
179-
if (arg_loc && (arg_loc.start_offset...arg_loc.end_offset).cover?(char_position)) ||
180-
(blk_loc && (blk_loc.start_offset...blk_loc.end_offset).cover?(char_position))
181-
call_node = candidate
182-
end
183-
end
184-
185-
# If there are node types to filter by, and the current node is not one of those types, then skip it
186-
next if node_types.any? && node_types.none? { |type| candidate.class == type }
187-
188-
# If the current node is narrower than or equal to the previous closest node, then it is more precise
189-
closest_loc = closest.location
190-
if loc.end_offset - loc.start_offset <= closest_loc.end_offset - closest_loc.start_offset
191-
parent = closest
192-
closest = candidate
193-
end
194-
end
195-
196-
# When targeting the constant part of a class/module definition, we do not want the nesting to be duplicated. That
197-
# is, when targeting Bar in the following example:
198-
#
199-
# ```ruby
200-
# class Foo::Bar; end
201-
# ```
202-
# The correct target is `Foo::Bar` with an empty nesting. `Foo::Bar` should not appear in the nesting stack, even
203-
# though the class/module node does indeed enclose the target, because it would lead to incorrect behavior
204-
if closest.is_a?(Prism::ConstantReadNode) || closest.is_a?(Prism::ConstantPathNode)
205-
last_level = nesting_nodes.last
206-
207-
if (last_level.is_a?(Prism::ModuleNode) || last_level.is_a?(Prism::ClassNode)) &&
208-
last_level.constant_path == closest
209-
nesting_nodes.pop
210-
end
211-
end
212-
213-
NodeContext.new(closest, parent, nesting_nodes, call_node)
214-
end
215-
216110
class Scanner
217111
extend T::Sig
218112

lib/ruby_lsp/erb_document.rb

+14-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
module RubyLsp
55
class ERBDocument < Document
66
extend T::Sig
7+
extend T::Generic
78

8-
sig { override.returns(Prism::ParseResult) }
9+
ParseResultType = type_member { { fixed: Prism::ParseResult } }
10+
11+
sig { override.returns(ParseResultType) }
912
def parse
1013
return @parse_result unless @needs_parsing
1114

@@ -26,6 +29,16 @@ def language_id
2629
LanguageId::ERB
2730
end
2831

32+
sig do
33+
params(
34+
position: T::Hash[Symbol, T.untyped],
35+
node_types: T::Array[T.class_of(Prism::Node)],
36+
).returns(NodeContext)
37+
end
38+
def locate_node(position, node_types: [])
39+
RubyDocument.locate(@parse_result.value, create_scanner.find_char_position(position), node_types: node_types)
40+
end
41+
2942
class ERBScanner
3043
extend T::Sig
3144

lib/ruby_lsp/requests/code_action_resolve.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ def refactor_variable
112112
extracted_source = T.must(@document.source[start_index...end_index])
113113

114114
# Find the closest statements node, so that we place the refactor in a valid position
115-
node_context = @document
115+
node_context = RubyDocument
116116
.locate(@document.parse_result.value, start_index, node_types: [Prism::StatementsNode, Prism::BlockNode])
117117

118118
closest_statements = node_context.node
@@ -206,7 +206,7 @@ def refactor_method
206206
extracted_source = T.must(@document.source[start_index...end_index])
207207

208208
# Find the closest method declaration node, so that we place the refactor in a valid position
209-
node_context = @document.locate(@document.parse_result.value, start_index, node_types: [Prism::DefNode])
209+
node_context = RubyDocument.locate(@document.parse_result.value, start_index, node_types: [Prism::DefNode])
210210
closest_def = T.cast(node_context.node, Prism::DefNode)
211211
return Error::InvalidTargetRange if closest_def.nil?
212212

lib/ruby_lsp/requests/code_actions.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def provider
3737

3838
sig do
3939
params(
40-
document: Document,
40+
document: T.any(RubyDocument, ERBDocument),
4141
range: T::Hash[Symbol, T.untyped],
4242
context: T::Hash[Symbol, T.untyped],
4343
).void

lib/ruby_lsp/requests/completion.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def provider
4646

4747
sig do
4848
params(
49-
document: Document,
49+
document: T.any(RubyDocument, ERBDocument),
5050
global_state: GlobalState,
5151
params: T::Hash[Symbol, T.untyped],
5252
sorbet_level: RubyDocument::SorbetLevel,
@@ -60,7 +60,7 @@ def initialize(document, global_state, params, sorbet_level, dispatcher)
6060
# Completion always receives the position immediately after the character that was just typed. Here we adjust it
6161
# back by 1, so that we find the right node
6262
char_position = document.create_scanner.find_char_position(params[:position]) - 1
63-
node_context = document.locate(
63+
node_context = RubyDocument.locate(
6464
document.parse_result.value,
6565
char_position,
6666
node_types: [

lib/ruby_lsp/requests/definition.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class Definition < Request
3838

3939
sig do
4040
params(
41-
document: Document,
41+
document: T.any(RubyDocument, ERBDocument),
4242
global_state: GlobalState,
4343
position: T::Hash[Symbol, T.untyped],
4444
dispatcher: Prism::Dispatcher,

lib/ruby_lsp/requests/diagnostics.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def provider
3232
end
3333
end
3434

35-
sig { params(global_state: GlobalState, document: Document).void }
35+
sig { params(global_state: GlobalState, document: RubyDocument).void }
3636
def initialize(global_state, document)
3737
super()
3838
@active_linters = T.let(global_state.active_linters, T::Array[Support::Formatter])

lib/ruby_lsp/requests/document_highlight.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class DocumentHighlight < Request
2929

3030
sig do
3131
params(
32-
document: Document,
32+
document: T.any(RubyDocument, ERBDocument),
3333
position: T::Hash[Symbol, T.untyped],
3434
dispatcher: Prism::Dispatcher,
3535
).void

lib/ruby_lsp/requests/formatting.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def provider
4040
end
4141
end
4242

43-
sig { params(global_state: GlobalState, document: Document).void }
43+
sig { params(global_state: GlobalState, document: RubyDocument).void }
4444
def initialize(global_state, document)
4545
super()
4646
@document = document

lib/ruby_lsp/requests/hover.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def provider
3232

3333
sig do
3434
params(
35-
document: Document,
35+
document: T.any(RubyDocument, ERBDocument),
3636
global_state: GlobalState,
3737
position: T::Hash[Symbol, T.untyped],
3838
dispatcher: Prism::Dispatcher,

lib/ruby_lsp/requests/inlay_hints.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def provider
5252

5353
sig do
5454
params(
55-
document: Document,
55+
document: T.any(RubyDocument, ERBDocument),
5656
range: T::Hash[Symbol, T.untyped],
5757
hints_configuration: RequestConfig,
5858
dispatcher: Prism::Dispatcher,

lib/ruby_lsp/requests/on_type_formatting.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def provider
4242

4343
sig do
4444
params(
45-
document: Document,
45+
document: RubyDocument,
4646
position: T::Hash[Symbol, T.untyped],
4747
trigger_character: String,
4848
client_name: String,

lib/ruby_lsp/requests/prepare_type_hierarchy.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def provider
3535

3636
sig do
3737
params(
38-
document: Document,
38+
document: T.any(RubyDocument, ERBDocument),
3939
index: RubyIndexer::Index,
4040
position: T::Hash[Symbol, T.untyped],
4141
).void

lib/ruby_lsp/requests/selection_ranges.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ module Requests
2323
class SelectionRanges < Request
2424
extend T::Sig
2525
include Support::Common
26-
sig { params(document: Document).void }
26+
27+
sig { params(document: T.any(RubyDocument, ERBDocument)).void }
2728
def initialize(document)
2829
super()
2930
@document = document

lib/ruby_lsp/requests/show_syntax_tree.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ module Requests
2020
class ShowSyntaxTree < Request
2121
extend T::Sig
2222

23-
sig { params(document: Document, range: T.nilable(T::Hash[Symbol, T.untyped])).void }
23+
sig { params(document: RubyDocument, range: T.nilable(T::Hash[Symbol, T.untyped])).void }
2424
def initialize(document, range)
2525
super()
2626
@document = document

lib/ruby_lsp/requests/signature_help.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def provider
4141

4242
sig do
4343
params(
44-
document: Document,
44+
document: T.any(RubyDocument, ERBDocument),
4545
global_state: GlobalState,
4646
position: T::Hash[Symbol, T.untyped],
4747
context: T.nilable(T::Hash[Symbol, T.untyped]),

lib/ruby_lsp/requests/support/formatter.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ module Formatter
1010

1111
interface!
1212

13-
sig { abstract.params(uri: URI::Generic, document: Document).returns(T.nilable(String)) }
13+
sig { abstract.params(uri: URI::Generic, document: RubyDocument).returns(T.nilable(String)) }
1414
def run_formatting(uri, document); end
1515

1616
sig do
1717
abstract.params(
1818
uri: URI::Generic,
19-
document: Document,
19+
document: RubyDocument,
2020
).returns(T.nilable(T::Array[Interface::Diagnostic]))
2121
end
2222
def run_diagnostic(uri, document); end

lib/ruby_lsp/requests/support/rubocop_diagnostic.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class RuboCopDiagnostic
3131

3232
# TODO: avoid passing document once we have alternative ways to get at
3333
# encoding and file source
34-
sig { params(document: Document, offense: RuboCop::Cop::Offense, uri: URI::Generic).void }
34+
sig { params(document: RubyDocument, offense: RuboCop::Cop::Offense, uri: URI::Generic).void }
3535
def initialize(document, offense, uri)
3636
@document = document
3737
@offense = offense

lib/ruby_lsp/requests/support/rubocop_formatter.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def initialize
1717
@format_runner = T.let(RuboCopRunner.new("-a"), RuboCopRunner)
1818
end
1919

20-
sig { override.params(uri: URI::Generic, document: Document).returns(T.nilable(String)) }
20+
sig { override.params(uri: URI::Generic, document: RubyDocument).returns(T.nilable(String)) }
2121
def run_formatting(uri, document)
2222
filename = T.must(uri.to_standardized_path || uri.opaque)
2323

@@ -29,7 +29,7 @@ def run_formatting(uri, document)
2929
sig do
3030
override.params(
3131
uri: URI::Generic,
32-
document: Document,
32+
document: RubyDocument,
3333
).returns(T.nilable(T::Array[Interface::Diagnostic]))
3434
end
3535
def run_diagnostic(uri, document)

lib/ruby_lsp/requests/support/syntax_tree_formatter.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def initialize
2929
)
3030
end
3131

32-
sig { override.params(uri: URI::Generic, document: Document).returns(T.nilable(String)) }
32+
sig { override.params(uri: URI::Generic, document: RubyDocument).returns(T.nilable(String)) }
3333
def run_formatting(uri, document)
3434
path = uri.to_standardized_path
3535
return if path && @options.ignore_files.any? { |pattern| File.fnmatch?("*/#{pattern}", path) }
@@ -40,7 +40,7 @@ def run_formatting(uri, document)
4040
sig do
4141
override.params(
4242
uri: URI::Generic,
43-
document: Document,
43+
document: RubyDocument,
4444
).returns(T.nilable(T::Array[Interface::Diagnostic]))
4545
end
4646
def run_diagnostic(uri, document)

0 commit comments

Comments
 (0)