ruby-lsp 0.17.12 → 0.17.14
Sign up to get free protection for your applications and to get access to all the features.
- 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
|