ruby-lsp 0.17.12 → 0.17.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/exe/ruby-lsp +6 -4
- data/exe/ruby-lsp-check +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +6 -1
- data/lib/ruby_indexer/ruby_indexer.rb +0 -8
- data/lib/ruby_indexer/test/configuration_test.rb +1 -1
- data/lib/ruby_lsp/addon.rb +9 -4
- data/lib/ruby_lsp/base_server.rb +7 -2
- data/lib/ruby_lsp/document.rb +9 -178
- data/lib/ruby_lsp/erb_document.rb +16 -2
- data/lib/ruby_lsp/global_state.rb +34 -14
- data/lib/ruby_lsp/internal.rb +1 -0
- data/lib/ruby_lsp/listeners/completion.rb +5 -5
- data/lib/ruby_lsp/listeners/definition.rb +3 -3
- data/lib/ruby_lsp/listeners/hover.rb +5 -5
- data/lib/ruby_lsp/listeners/signature_help.rb +1 -1
- data/lib/ruby_lsp/rbs_document.rb +41 -0
- data/lib/ruby_lsp/requests/code_action_resolve.rb +37 -21
- data/lib/ruby_lsp/requests/code_actions.rb +3 -3
- data/lib/ruby_lsp/requests/completion.rb +3 -3
- data/lib/ruby_lsp/requests/definition.rb +2 -2
- data/lib/ruby_lsp/requests/diagnostics.rb +1 -1
- data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
- data/lib/ruby_lsp/requests/formatting.rb +1 -1
- data/lib/ruby_lsp/requests/hover.rb +2 -2
- data/lib/ruby_lsp/requests/inlay_hints.rb +1 -1
- data/lib/ruby_lsp/requests/on_type_formatting.rb +1 -1
- data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +1 -1
- data/lib/ruby_lsp/requests/selection_ranges.rb +2 -1
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +1 -1
- data/lib/ruby_lsp/requests/signature_help.rb +2 -2
- data/lib/ruby_lsp/requests/support/common.rb +2 -2
- data/lib/ruby_lsp/requests/support/formatter.rb +2 -2
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +1 -1
- data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +2 -2
- data/lib/ruby_lsp/requests/support/syntax_tree_formatter.rb +2 -2
- data/lib/ruby_lsp/ruby_document.rb +183 -1
- data/lib/ruby_lsp/server.rb +125 -15
- data/lib/ruby_lsp/setup_bundler.rb +15 -4
- data/lib/ruby_lsp/store.rb +10 -6
- data/lib/ruby_lsp/utils.rb +12 -0
- metadata +4 -3
@@ -0,0 +1,41 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
class RBSDocument < Document
|
6
|
+
extend T::Sig
|
7
|
+
extend T::Generic
|
8
|
+
|
9
|
+
ParseResultType = type_member { { fixed: T::Array[RBS::AST::Declarations::Base] } }
|
10
|
+
|
11
|
+
sig { params(source: String, version: Integer, uri: URI::Generic, encoding: Encoding).void }
|
12
|
+
def initialize(source:, version:, uri:, encoding: Encoding::UTF_8)
|
13
|
+
@syntax_error = T.let(false, T::Boolean)
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
sig { override.returns(ParseResultType) }
|
18
|
+
def parse
|
19
|
+
return @parse_result unless @needs_parsing
|
20
|
+
|
21
|
+
@needs_parsing = false
|
22
|
+
|
23
|
+
_, _, declarations = RBS::Parser.parse_signature(@source)
|
24
|
+
@syntax_error = false
|
25
|
+
@parse_result = declarations
|
26
|
+
rescue RBS::ParsingError
|
27
|
+
@syntax_error = true
|
28
|
+
@parse_result
|
29
|
+
end
|
30
|
+
|
31
|
+
sig { override.returns(T::Boolean) }
|
32
|
+
def syntax_error?
|
33
|
+
@syntax_error
|
34
|
+
end
|
35
|
+
|
36
|
+
sig { override.returns(LanguageId) }
|
37
|
+
def language_id
|
38
|
+
LanguageId::RBS
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -38,7 +38,7 @@ module RubyLsp
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
sig { params(document:
|
41
|
+
sig { params(document: RubyDocument, code_action: T::Hash[Symbol, T.untyped]).void }
|
42
42
|
def initialize(document, code_action)
|
43
43
|
super()
|
44
44
|
@document = document
|
@@ -54,7 +54,7 @@ module RubyLsp
|
|
54
54
|
refactor_variable
|
55
55
|
when CodeActions::EXTRACT_TO_METHOD_TITLE
|
56
56
|
refactor_method
|
57
|
-
when CodeActions::
|
57
|
+
when CodeActions::TOGGLE_BLOCK_STYLE_TITLE
|
58
58
|
switch_block_style
|
59
59
|
else
|
60
60
|
Error::UnknownCodeAction
|
@@ -81,7 +81,7 @@ module RubyLsp
|
|
81
81
|
indentation = " " * target.location.start_column unless node.opening_loc.slice == "do"
|
82
82
|
|
83
83
|
Interface::CodeAction.new(
|
84
|
-
title: CodeActions::
|
84
|
+
title: CodeActions::TOGGLE_BLOCK_STYLE_TITLE,
|
85
85
|
edit: Interface::WorkspaceEdit.new(
|
86
86
|
document_changes: [
|
87
87
|
Interface::TextDocumentEdit.new(
|
@@ -112,7 +112,7 @@ module RubyLsp
|
|
112
112
|
extracted_source = T.must(@document.source[start_index...end_index])
|
113
113
|
|
114
114
|
# Find the closest statements node, so that we place the refactor in a valid position
|
115
|
-
node_context =
|
115
|
+
node_context = RubyDocument
|
116
116
|
.locate(@document.parse_result.value, start_index, node_types: [Prism::StatementsNode, Prism::BlockNode])
|
117
117
|
|
118
118
|
closest_statements = node_context.node
|
@@ -206,28 +206,44 @@ module RubyLsp
|
|
206
206
|
extracted_source = T.must(@document.source[start_index...end_index])
|
207
207
|
|
208
208
|
# Find the closest method declaration node, so that we place the refactor in a valid position
|
209
|
-
node_context =
|
210
|
-
|
211
|
-
return Error::InvalidTargetRange
|
209
|
+
node_context = RubyDocument.locate(@document.parse_result.value, start_index, node_types: [Prism::DefNode])
|
210
|
+
closest_node = node_context.node
|
211
|
+
return Error::InvalidTargetRange unless closest_node
|
212
212
|
|
213
|
-
|
214
|
-
|
213
|
+
target_range = if closest_node.is_a?(Prism::DefNode)
|
214
|
+
end_keyword_loc = closest_node.end_keyword_loc
|
215
|
+
return Error::InvalidTargetRange unless end_keyword_loc
|
215
216
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
target_range = {
|
220
|
-
start: { line: end_line, character: character },
|
221
|
-
end: { line: end_line, character: character },
|
222
|
-
}
|
217
|
+
end_line = end_keyword_loc.end_line - 1
|
218
|
+
character = end_keyword_loc.end_column
|
219
|
+
indentation = " " * end_keyword_loc.start_column
|
223
220
|
|
224
|
-
|
221
|
+
new_method_source = <<~RUBY.chomp
|
225
222
|
|
226
223
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
224
|
+
#{indentation}def #{NEW_METHOD_NAME}
|
225
|
+
#{indentation} #{extracted_source}
|
226
|
+
#{indentation}end
|
227
|
+
RUBY
|
228
|
+
|
229
|
+
{
|
230
|
+
start: { line: end_line, character: character },
|
231
|
+
end: { line: end_line, character: character },
|
232
|
+
}
|
233
|
+
else
|
234
|
+
new_method_source = <<~RUBY
|
235
|
+
#{indentation}def #{NEW_METHOD_NAME}
|
236
|
+
#{indentation} #{extracted_source.gsub("\n", "\n ")}
|
237
|
+
#{indentation}end
|
238
|
+
|
239
|
+
RUBY
|
240
|
+
|
241
|
+
line = [0, source_range.dig(:start, :line) - 1].max
|
242
|
+
{
|
243
|
+
start: { line: line, character: source_range.dig(:start, :character) },
|
244
|
+
end: { line: line, character: source_range.dig(:start, :character) },
|
245
|
+
}
|
246
|
+
end
|
231
247
|
|
232
248
|
Interface::CodeAction.new(
|
233
249
|
title: CodeActions::EXTRACT_TO_METHOD_TITLE,
|
@@ -21,7 +21,7 @@ module RubyLsp
|
|
21
21
|
|
22
22
|
EXTRACT_TO_VARIABLE_TITLE = "Refactor: Extract Variable"
|
23
23
|
EXTRACT_TO_METHOD_TITLE = "Refactor: Extract Method"
|
24
|
-
|
24
|
+
TOGGLE_BLOCK_STYLE_TITLE = "Refactor: Toggle block style"
|
25
25
|
|
26
26
|
class << self
|
27
27
|
extend T::Sig
|
@@ -37,7 +37,7 @@ module RubyLsp
|
|
37
37
|
|
38
38
|
sig do
|
39
39
|
params(
|
40
|
-
document:
|
40
|
+
document: T.any(RubyDocument, ERBDocument),
|
41
41
|
range: T::Hash[Symbol, T.untyped],
|
42
42
|
context: T::Hash[Symbol, T.untyped],
|
43
43
|
).void
|
@@ -71,7 +71,7 @@ module RubyLsp
|
|
71
71
|
data: { range: @range, uri: @uri.to_s },
|
72
72
|
)
|
73
73
|
code_actions << Interface::CodeAction.new(
|
74
|
-
title:
|
74
|
+
title: TOGGLE_BLOCK_STYLE_TITLE,
|
75
75
|
kind: Constant::CodeActionKind::REFACTOR_REWRITE,
|
76
76
|
data: { range: @range, uri: @uri.to_s },
|
77
77
|
)
|
@@ -46,10 +46,10 @@ module RubyLsp
|
|
46
46
|
|
47
47
|
sig do
|
48
48
|
params(
|
49
|
-
document:
|
49
|
+
document: T.any(RubyDocument, ERBDocument),
|
50
50
|
global_state: GlobalState,
|
51
51
|
params: T::Hash[Symbol, T.untyped],
|
52
|
-
sorbet_level:
|
52
|
+
sorbet_level: RubyDocument::SorbetLevel,
|
53
53
|
dispatcher: Prism::Dispatcher,
|
54
54
|
).void
|
55
55
|
end
|
@@ -60,7 +60,7 @@ module RubyLsp
|
|
60
60
|
# Completion always receives the position immediately after the character that was just typed. Here we adjust it
|
61
61
|
# back by 1, so that we find the right node
|
62
62
|
char_position = document.create_scanner.find_char_position(params[:position]) - 1
|
63
|
-
node_context =
|
63
|
+
node_context = RubyDocument.locate(
|
64
64
|
document.parse_result.value,
|
65
65
|
char_position,
|
66
66
|
node_types: [
|
@@ -38,11 +38,11 @@ module RubyLsp
|
|
38
38
|
|
39
39
|
sig do
|
40
40
|
params(
|
41
|
-
document:
|
41
|
+
document: T.any(RubyDocument, ERBDocument),
|
42
42
|
global_state: GlobalState,
|
43
43
|
position: T::Hash[Symbol, T.untyped],
|
44
44
|
dispatcher: Prism::Dispatcher,
|
45
|
-
sorbet_level:
|
45
|
+
sorbet_level: RubyDocument::SorbetLevel,
|
46
46
|
).void
|
47
47
|
end
|
48
48
|
def initialize(document, global_state, position, dispatcher, sorbet_level)
|
@@ -32,7 +32,7 @@ module RubyLsp
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
sig { params(global_state: GlobalState, document:
|
35
|
+
sig { params(global_state: GlobalState, document: RubyDocument).void }
|
36
36
|
def initialize(global_state, document)
|
37
37
|
super()
|
38
38
|
@active_linters = T.let(global_state.active_linters, T::Array[Support::Formatter])
|
@@ -32,11 +32,11 @@ module RubyLsp
|
|
32
32
|
|
33
33
|
sig do
|
34
34
|
params(
|
35
|
-
document:
|
35
|
+
document: T.any(RubyDocument, ERBDocument),
|
36
36
|
global_state: GlobalState,
|
37
37
|
position: T::Hash[Symbol, T.untyped],
|
38
38
|
dispatcher: Prism::Dispatcher,
|
39
|
-
sorbet_level:
|
39
|
+
sorbet_level: RubyDocument::SorbetLevel,
|
40
40
|
).void
|
41
41
|
end
|
42
42
|
def initialize(document, global_state, position, dispatcher, sorbet_level)
|
@@ -23,7 +23,8 @@ module RubyLsp
|
|
23
23
|
class SelectionRanges < Request
|
24
24
|
extend T::Sig
|
25
25
|
include Support::Common
|
26
|
-
|
26
|
+
|
27
|
+
sig { params(document: T.any(RubyDocument, ERBDocument)).void }
|
27
28
|
def initialize(document)
|
28
29
|
super()
|
29
30
|
@document = document
|
@@ -20,7 +20,7 @@ module RubyLsp
|
|
20
20
|
class ShowSyntaxTree < Request
|
21
21
|
extend T::Sig
|
22
22
|
|
23
|
-
sig { params(document:
|
23
|
+
sig { params(document: RubyDocument, range: T.nilable(T::Hash[Symbol, T.untyped])).void }
|
24
24
|
def initialize(document, range)
|
25
25
|
super()
|
26
26
|
@document = document
|
@@ -41,12 +41,12 @@ module RubyLsp
|
|
41
41
|
|
42
42
|
sig do
|
43
43
|
params(
|
44
|
-
document:
|
44
|
+
document: T.any(RubyDocument, ERBDocument),
|
45
45
|
global_state: GlobalState,
|
46
46
|
position: T::Hash[Symbol, T.untyped],
|
47
47
|
context: T.nilable(T::Hash[Symbol, T.untyped]),
|
48
48
|
dispatcher: Prism::Dispatcher,
|
49
|
-
sorbet_level:
|
49
|
+
sorbet_level: RubyDocument::SorbetLevel,
|
50
50
|
).void
|
51
51
|
end
|
52
52
|
def initialize(document, global_state, position, context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
|
@@ -209,9 +209,9 @@ module RubyLsp
|
|
209
209
|
end
|
210
210
|
end
|
211
211
|
|
212
|
-
sig { params(sorbet_level:
|
212
|
+
sig { params(sorbet_level: RubyDocument::SorbetLevel).returns(T::Boolean) }
|
213
213
|
def sorbet_level_true_or_higher?(sorbet_level)
|
214
|
-
sorbet_level ==
|
214
|
+
sorbet_level == RubyDocument::SorbetLevel::True || sorbet_level == RubyDocument::SorbetLevel::Strict
|
215
215
|
end
|
216
216
|
end
|
217
217
|
end
|
@@ -10,13 +10,13 @@ module RubyLsp
|
|
10
10
|
|
11
11
|
interface!
|
12
12
|
|
13
|
-
sig { abstract.params(uri: URI::Generic, document:
|
13
|
+
sig { abstract.params(uri: URI::Generic, document: RubyDocument).returns(T.nilable(String)) }
|
14
14
|
def run_formatting(uri, document); end
|
15
15
|
|
16
16
|
sig do
|
17
17
|
abstract.params(
|
18
18
|
uri: URI::Generic,
|
19
|
-
document:
|
19
|
+
document: RubyDocument,
|
20
20
|
).returns(T.nilable(T::Array[Interface::Diagnostic]))
|
21
21
|
end
|
22
22
|
def run_diagnostic(uri, document); end
|
@@ -31,7 +31,7 @@ module RubyLsp
|
|
31
31
|
|
32
32
|
# TODO: avoid passing document once we have alternative ways to get at
|
33
33
|
# encoding and file source
|
34
|
-
sig { params(document:
|
34
|
+
sig { params(document: RubyDocument, offense: RuboCop::Cop::Offense, uri: URI::Generic).void }
|
35
35
|
def initialize(document, offense, uri)
|
36
36
|
@document = document
|
37
37
|
@offense = offense
|
@@ -17,7 +17,7 @@ module RubyLsp
|
|
17
17
|
@format_runner = T.let(RuboCopRunner.new("-a"), RuboCopRunner)
|
18
18
|
end
|
19
19
|
|
20
|
-
sig { override.params(uri: URI::Generic, document:
|
20
|
+
sig { override.params(uri: URI::Generic, document: RubyDocument).returns(T.nilable(String)) }
|
21
21
|
def run_formatting(uri, document)
|
22
22
|
filename = T.must(uri.to_standardized_path || uri.opaque)
|
23
23
|
|
@@ -29,7 +29,7 @@ module RubyLsp
|
|
29
29
|
sig do
|
30
30
|
override.params(
|
31
31
|
uri: URI::Generic,
|
32
|
-
document:
|
32
|
+
document: RubyDocument,
|
33
33
|
).returns(T.nilable(T::Array[Interface::Diagnostic]))
|
34
34
|
end
|
35
35
|
def run_diagnostic(uri, document)
|
@@ -29,7 +29,7 @@ module RubyLsp
|
|
29
29
|
)
|
30
30
|
end
|
31
31
|
|
32
|
-
sig { override.params(uri: URI::Generic, document:
|
32
|
+
sig { override.params(uri: URI::Generic, document: RubyDocument).returns(T.nilable(String)) }
|
33
33
|
def run_formatting(uri, document)
|
34
34
|
path = uri.to_standardized_path
|
35
35
|
return if path && @options.ignore_files.any? { |pattern| File.fnmatch?("*/#{pattern}", path) }
|
@@ -40,7 +40,7 @@ module RubyLsp
|
|
40
40
|
sig do
|
41
41
|
override.params(
|
42
42
|
uri: URI::Generic,
|
43
|
-
document:
|
43
|
+
document: RubyDocument,
|
44
44
|
).returns(T.nilable(T::Array[Interface::Diagnostic]))
|
45
45
|
end
|
46
46
|
def run_diagnostic(uri, document)
|
@@ -3,7 +3,125 @@
|
|
3
3
|
|
4
4
|
module RubyLsp
|
5
5
|
class RubyDocument < Document
|
6
|
-
|
6
|
+
extend T::Sig
|
7
|
+
extend T::Generic
|
8
|
+
|
9
|
+
ParseResultType = type_member { { fixed: Prism::ParseResult } }
|
10
|
+
|
11
|
+
class SorbetLevel < T::Enum
|
12
|
+
enums do
|
13
|
+
None = new("none")
|
14
|
+
Ignore = new("ignore")
|
15
|
+
False = new("false")
|
16
|
+
True = new("true")
|
17
|
+
Strict = new("strict")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class << self
|
22
|
+
extend T::Sig
|
23
|
+
|
24
|
+
sig do
|
25
|
+
params(
|
26
|
+
node: Prism::Node,
|
27
|
+
char_position: Integer,
|
28
|
+
node_types: T::Array[T.class_of(Prism::Node)],
|
29
|
+
).returns(NodeContext)
|
30
|
+
end
|
31
|
+
def locate(node, char_position, node_types: [])
|
32
|
+
queue = T.let(node.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
|
33
|
+
closest = node
|
34
|
+
parent = T.let(nil, T.nilable(Prism::Node))
|
35
|
+
nesting_nodes = T.let(
|
36
|
+
[],
|
37
|
+
T::Array[T.any(
|
38
|
+
Prism::ClassNode,
|
39
|
+
Prism::ModuleNode,
|
40
|
+
Prism::SingletonClassNode,
|
41
|
+
Prism::DefNode,
|
42
|
+
Prism::BlockNode,
|
43
|
+
Prism::LambdaNode,
|
44
|
+
Prism::ProgramNode,
|
45
|
+
)],
|
46
|
+
)
|
47
|
+
|
48
|
+
nesting_nodes << node if node.is_a?(Prism::ProgramNode)
|
49
|
+
call_node = T.let(nil, T.nilable(Prism::CallNode))
|
50
|
+
|
51
|
+
until queue.empty?
|
52
|
+
candidate = queue.shift
|
53
|
+
|
54
|
+
# Skip nil child nodes
|
55
|
+
next if candidate.nil?
|
56
|
+
|
57
|
+
# Add the next child_nodes to the queue to be processed. The order here is important! We want to move in the
|
58
|
+
# same order as the visiting mechanism, which means searching the child nodes before moving on to the next
|
59
|
+
# sibling
|
60
|
+
T.unsafe(queue).unshift(*candidate.child_nodes)
|
61
|
+
|
62
|
+
# Skip if the current node doesn't cover the desired position
|
63
|
+
loc = candidate.location
|
64
|
+
next unless (loc.start_offset...loc.end_offset).cover?(char_position)
|
65
|
+
|
66
|
+
# If the node's start character is already past the position, then we should've found the closest node
|
67
|
+
# already
|
68
|
+
break if char_position < loc.start_offset
|
69
|
+
|
70
|
+
# If the candidate starts after the end of the previous nesting level, then we've exited that nesting level
|
71
|
+
# and need to pop the stack
|
72
|
+
previous_level = nesting_nodes.last
|
73
|
+
nesting_nodes.pop if previous_level && loc.start_offset > previous_level.location.end_offset
|
74
|
+
|
75
|
+
# Keep track of the nesting where we found the target. This is used to determine the fully qualified name of
|
76
|
+
# the target when it is a constant
|
77
|
+
case candidate
|
78
|
+
when Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefNode, Prism::BlockNode,
|
79
|
+
Prism::LambdaNode
|
80
|
+
nesting_nodes << candidate
|
81
|
+
end
|
82
|
+
|
83
|
+
if candidate.is_a?(Prism::CallNode)
|
84
|
+
arg_loc = candidate.arguments&.location
|
85
|
+
blk_loc = candidate.block&.location
|
86
|
+
if (arg_loc && (arg_loc.start_offset...arg_loc.end_offset).cover?(char_position)) ||
|
87
|
+
(blk_loc && (blk_loc.start_offset...blk_loc.end_offset).cover?(char_position))
|
88
|
+
call_node = candidate
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# If there are node types to filter by, and the current node is not one of those types, then skip it
|
93
|
+
next if node_types.any? && node_types.none? { |type| candidate.class == type }
|
94
|
+
|
95
|
+
# If the current node is narrower than or equal to the previous closest node, then it is more precise
|
96
|
+
closest_loc = closest.location
|
97
|
+
if loc.end_offset - loc.start_offset <= closest_loc.end_offset - closest_loc.start_offset
|
98
|
+
parent = closest
|
99
|
+
closest = candidate
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# When targeting the constant part of a class/module definition, we do not want the nesting to be duplicated.
|
104
|
+
# That is, when targeting Bar in the following example:
|
105
|
+
#
|
106
|
+
# ```ruby
|
107
|
+
# class Foo::Bar; end
|
108
|
+
# ```
|
109
|
+
# The correct target is `Foo::Bar` with an empty nesting. `Foo::Bar` should not appear in the nesting stack,
|
110
|
+
# even though the class/module node does indeed enclose the target, because it would lead to incorrect behavior
|
111
|
+
if closest.is_a?(Prism::ConstantReadNode) || closest.is_a?(Prism::ConstantPathNode)
|
112
|
+
last_level = nesting_nodes.last
|
113
|
+
|
114
|
+
if (last_level.is_a?(Prism::ModuleNode) || last_level.is_a?(Prism::ClassNode)) &&
|
115
|
+
last_level.constant_path == closest
|
116
|
+
nesting_nodes.pop
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
NodeContext.new(closest, parent, nesting_nodes, call_node)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
sig { override.returns(ParseResultType) }
|
7
125
|
def parse
|
8
126
|
return @parse_result unless @needs_parsing
|
9
127
|
|
@@ -20,5 +138,69 @@ module RubyLsp
|
|
20
138
|
def language_id
|
21
139
|
LanguageId::Ruby
|
22
140
|
end
|
141
|
+
|
142
|
+
sig { returns(SorbetLevel) }
|
143
|
+
def sorbet_level
|
144
|
+
sigil = parse_result.magic_comments.find do |comment|
|
145
|
+
comment.key == "typed"
|
146
|
+
end&.value
|
147
|
+
|
148
|
+
case sigil
|
149
|
+
when "ignore"
|
150
|
+
SorbetLevel::Ignore
|
151
|
+
when "false"
|
152
|
+
SorbetLevel::False
|
153
|
+
when "true"
|
154
|
+
SorbetLevel::True
|
155
|
+
when "strict", "strong"
|
156
|
+
SorbetLevel::Strict
|
157
|
+
else
|
158
|
+
SorbetLevel::None
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
sig do
|
163
|
+
params(
|
164
|
+
range: T::Hash[Symbol, T.untyped],
|
165
|
+
node_types: T::Array[T.class_of(Prism::Node)],
|
166
|
+
).returns(T.nilable(Prism::Node))
|
167
|
+
end
|
168
|
+
def locate_first_within_range(range, node_types: [])
|
169
|
+
scanner = create_scanner
|
170
|
+
start_position = scanner.find_char_position(range[:start])
|
171
|
+
end_position = scanner.find_char_position(range[:end])
|
172
|
+
desired_range = (start_position...end_position)
|
173
|
+
queue = T.let(@parse_result.value.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
|
174
|
+
|
175
|
+
until queue.empty?
|
176
|
+
candidate = queue.shift
|
177
|
+
|
178
|
+
# Skip nil child nodes
|
179
|
+
next if candidate.nil?
|
180
|
+
|
181
|
+
# Add the next child_nodes to the queue to be processed. The order here is important! We want to move in the
|
182
|
+
# same order as the visiting mechanism, which means searching the child nodes before moving on to the next
|
183
|
+
# sibling
|
184
|
+
T.unsafe(queue).unshift(*candidate.child_nodes)
|
185
|
+
|
186
|
+
# Skip if the current node doesn't cover the desired position
|
187
|
+
loc = candidate.location
|
188
|
+
|
189
|
+
if desired_range.cover?(loc.start_offset...loc.end_offset) &&
|
190
|
+
(node_types.empty? || node_types.any? { |type| candidate.class == type })
|
191
|
+
return candidate
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
sig do
|
197
|
+
params(
|
198
|
+
position: T::Hash[Symbol, T.untyped],
|
199
|
+
node_types: T::Array[T.class_of(Prism::Node)],
|
200
|
+
).returns(NodeContext)
|
201
|
+
end
|
202
|
+
def locate_node(position, node_types: [])
|
203
|
+
RubyDocument.locate(@parse_result.value, create_scanner.find_char_position(position), node_types: node_types)
|
204
|
+
end
|
23
205
|
end
|
24
206
|
end
|