ruby-lsp 0.17.4 → 0.17.13
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/README.md +11 -2
- data/VERSION +1 -1
- data/exe/ruby-lsp +26 -1
- data/exe/ruby-lsp-check +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +74 -43
- data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +26 -0
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +147 -29
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +383 -79
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +195 -61
- data/lib/ruby_indexer/ruby_indexer.rb +1 -8
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +71 -3
- data/lib/ruby_indexer/test/configuration_test.rb +1 -1
- data/lib/ruby_indexer/test/constant_test.rb +17 -17
- data/lib/ruby_indexer/test/enhancements_test.rb +197 -0
- data/lib/ruby_indexer/test/index_test.rb +367 -17
- data/lib/ruby_indexer/test/method_test.rb +58 -25
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +297 -0
- data/lib/ruby_indexer/test/test_case.rb +1 -5
- data/lib/ruby_lsp/addon.rb +22 -5
- data/lib/ruby_lsp/base_server.rb +8 -3
- data/lib/ruby_lsp/document.rb +27 -46
- data/lib/ruby_lsp/erb_document.rb +125 -0
- data/lib/ruby_lsp/global_state.rb +47 -19
- data/lib/ruby_lsp/internal.rb +2 -0
- data/lib/ruby_lsp/listeners/completion.rb +161 -57
- data/lib/ruby_lsp/listeners/definition.rb +91 -27
- data/lib/ruby_lsp/listeners/document_highlight.rb +5 -1
- data/lib/ruby_lsp/listeners/hover.rb +61 -19
- data/lib/ruby_lsp/listeners/signature_help.rb +13 -6
- data/lib/ruby_lsp/node_context.rb +65 -5
- data/lib/ruby_lsp/requests/code_action_resolve.rb +107 -9
- data/lib/ruby_lsp/requests/code_actions.rb +11 -2
- data/lib/ruby_lsp/requests/completion.rb +4 -4
- data/lib/ruby_lsp/requests/completion_resolve.rb +14 -9
- data/lib/ruby_lsp/requests/definition.rb +18 -8
- data/lib/ruby_lsp/requests/diagnostics.rb +6 -5
- data/lib/ruby_lsp/requests/document_symbol.rb +2 -7
- data/lib/ruby_lsp/requests/folding_ranges.rb +6 -2
- data/lib/ruby_lsp/requests/formatting.rb +15 -0
- data/lib/ruby_lsp/requests/hover.rb +5 -5
- data/lib/ruby_lsp/requests/on_type_formatting.rb +6 -4
- data/lib/ruby_lsp/requests/selection_ranges.rb +1 -1
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +3 -2
- data/lib/ruby_lsp/requests/signature_help.rb +3 -3
- data/lib/ruby_lsp/requests/support/common.rb +11 -2
- data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +2 -6
- data/lib/ruby_lsp/ruby_document.rb +74 -0
- data/lib/ruby_lsp/server.rb +129 -54
- data/lib/ruby_lsp/store.rb +33 -9
- data/lib/ruby_lsp/test_helper.rb +3 -1
- data/lib/ruby_lsp/type_inferrer.rb +61 -25
- data/lib/ruby_lsp/utils.rb +13 -0
- metadata +9 -8
- data/exe/ruby-lsp-doctor +0 -23
data/lib/ruby_lsp/document.rb
CHANGED
@@ -3,6 +3,13 @@
|
|
3
3
|
|
4
4
|
module RubyLsp
|
5
5
|
class Document
|
6
|
+
class LanguageId < T::Enum
|
7
|
+
enums do
|
8
|
+
Ruby = new("ruby")
|
9
|
+
ERB = new("erb")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
6
13
|
extend T::Sig
|
7
14
|
extend T::Helpers
|
8
15
|
|
@@ -34,21 +41,14 @@ module RubyLsp
|
|
34
41
|
@parse_result = T.let(parse, Prism::ParseResult)
|
35
42
|
end
|
36
43
|
|
37
|
-
sig { returns(Prism::ProgramNode) }
|
38
|
-
def tree
|
39
|
-
@parse_result.value
|
40
|
-
end
|
41
|
-
|
42
|
-
sig { returns(T::Array[Prism::Comment]) }
|
43
|
-
def comments
|
44
|
-
@parse_result.comments
|
45
|
-
end
|
46
|
-
|
47
44
|
sig { params(other: Document).returns(T::Boolean) }
|
48
45
|
def ==(other)
|
49
|
-
@source == other.source
|
46
|
+
self.class == other.class && uri == other.uri && @source == other.source
|
50
47
|
end
|
51
48
|
|
49
|
+
sig { abstract.returns(LanguageId) }
|
50
|
+
def language_id; end
|
51
|
+
|
52
52
|
# TODO: remove this method once all nonpositional requests have been migrated to the listener pattern
|
53
53
|
sig do
|
54
54
|
type_parameters(:T)
|
@@ -96,10 +96,8 @@ module RubyLsp
|
|
96
96
|
sig { abstract.returns(Prism::ParseResult) }
|
97
97
|
def parse; end
|
98
98
|
|
99
|
-
sig { returns(T::Boolean) }
|
100
|
-
def syntax_error
|
101
|
-
@parse_result.failure?
|
102
|
-
end
|
99
|
+
sig { abstract.returns(T::Boolean) }
|
100
|
+
def syntax_error?; end
|
103
101
|
|
104
102
|
sig { returns(Scanner) }
|
105
103
|
def create_scanner
|
@@ -129,8 +127,18 @@ module RubyLsp
|
|
129
127
|
parent = T.let(nil, T.nilable(Prism::Node))
|
130
128
|
nesting_nodes = T.let(
|
131
129
|
[],
|
132
|
-
T::Array[T.any(
|
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
|
+
)],
|
133
139
|
)
|
140
|
+
|
141
|
+
nesting_nodes << node if node.is_a?(Prism::ProgramNode)
|
134
142
|
call_node = T.let(nil, T.nilable(Prism::CallNode))
|
135
143
|
|
136
144
|
until queue.empty?
|
@@ -160,11 +168,8 @@ module RubyLsp
|
|
160
168
|
# Keep track of the nesting where we found the target. This is used to determine the fully qualified name of the
|
161
169
|
# target when it is a constant
|
162
170
|
case candidate
|
163
|
-
when Prism::ClassNode, Prism::ModuleNode
|
164
|
-
|
165
|
-
when Prism::SingletonClassNode
|
166
|
-
nesting_nodes << candidate
|
167
|
-
when Prism::DefNode
|
171
|
+
when Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefNode, Prism::BlockNode,
|
172
|
+
Prism::LambdaNode
|
168
173
|
nesting_nodes << candidate
|
169
174
|
end
|
170
175
|
|
@@ -205,31 +210,7 @@ module RubyLsp
|
|
205
210
|
end
|
206
211
|
end
|
207
212
|
|
208
|
-
|
209
|
-
surrounding_method = T.let(nil, T.nilable(String))
|
210
|
-
|
211
|
-
nesting_nodes.each do |node|
|
212
|
-
case node
|
213
|
-
when Prism::ClassNode, Prism::ModuleNode
|
214
|
-
nesting << node.constant_path.slice
|
215
|
-
when Prism::SingletonClassNode
|
216
|
-
nesting << "<Class:#{nesting.last}>"
|
217
|
-
when Prism::DefNode
|
218
|
-
surrounding_method = node.name.to_s
|
219
|
-
next unless node.receiver.is_a?(Prism::SelfNode)
|
220
|
-
|
221
|
-
nesting << "<Class:#{nesting.last}>"
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
|
-
NodeContext.new(closest, parent, nesting, call_node, surrounding_method)
|
226
|
-
end
|
227
|
-
|
228
|
-
sig { returns(T::Boolean) }
|
229
|
-
def sorbet_sigil_is_true_or_higher
|
230
|
-
parse_result.magic_comments.any? do |comment|
|
231
|
-
comment.key == "typed" && ["true", "strict", "strong"].include?(comment.value)
|
232
|
-
end
|
213
|
+
NodeContext.new(closest, parent, nesting_nodes, call_node)
|
233
214
|
end
|
234
215
|
|
235
216
|
class Scanner
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
class ERBDocument < Document
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { override.returns(Prism::ParseResult) }
|
9
|
+
def parse
|
10
|
+
return @parse_result unless @needs_parsing
|
11
|
+
|
12
|
+
@needs_parsing = false
|
13
|
+
scanner = ERBScanner.new(@source)
|
14
|
+
scanner.scan
|
15
|
+
@parse_result = Prism.parse(scanner.ruby)
|
16
|
+
end
|
17
|
+
|
18
|
+
sig { override.returns(T::Boolean) }
|
19
|
+
def syntax_error?
|
20
|
+
@parse_result.failure?
|
21
|
+
end
|
22
|
+
|
23
|
+
sig { override.returns(LanguageId) }
|
24
|
+
def language_id
|
25
|
+
LanguageId::ERB
|
26
|
+
end
|
27
|
+
|
28
|
+
class ERBScanner
|
29
|
+
extend T::Sig
|
30
|
+
|
31
|
+
sig { returns(String) }
|
32
|
+
attr_reader :ruby, :html
|
33
|
+
|
34
|
+
sig { params(source: String).void }
|
35
|
+
def initialize(source)
|
36
|
+
@source = source
|
37
|
+
@html = T.let(+"", String)
|
38
|
+
@ruby = T.let(+"", String)
|
39
|
+
@current_pos = T.let(0, Integer)
|
40
|
+
@inside_ruby = T.let(false, T::Boolean)
|
41
|
+
end
|
42
|
+
|
43
|
+
sig { void }
|
44
|
+
def scan
|
45
|
+
while @current_pos < @source.length
|
46
|
+
scan_char
|
47
|
+
@current_pos += 1
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
sig { void }
|
54
|
+
def scan_char
|
55
|
+
char = @source[@current_pos]
|
56
|
+
|
57
|
+
case char
|
58
|
+
when "<"
|
59
|
+
if next_char == "%"
|
60
|
+
@inside_ruby = true
|
61
|
+
@current_pos += 1
|
62
|
+
push_char(" ")
|
63
|
+
|
64
|
+
if next_char == "=" && @source[@current_pos + 2] == "="
|
65
|
+
@current_pos += 2
|
66
|
+
push_char(" ")
|
67
|
+
elsif next_char == "=" || next_char == "-"
|
68
|
+
@current_pos += 1
|
69
|
+
push_char(" ")
|
70
|
+
end
|
71
|
+
else
|
72
|
+
push_char(T.must(char))
|
73
|
+
end
|
74
|
+
when "-"
|
75
|
+
if @inside_ruby && next_char == "%" &&
|
76
|
+
@source[@current_pos + 2] == ">"
|
77
|
+
@current_pos += 2
|
78
|
+
push_char(" ")
|
79
|
+
@inside_ruby = false
|
80
|
+
else
|
81
|
+
push_char(T.must(char))
|
82
|
+
end
|
83
|
+
when "%"
|
84
|
+
if @inside_ruby && next_char == ">"
|
85
|
+
@inside_ruby = false
|
86
|
+
@current_pos += 1
|
87
|
+
push_char(" ")
|
88
|
+
else
|
89
|
+
push_char(T.must(char))
|
90
|
+
end
|
91
|
+
when "\r"
|
92
|
+
@ruby << char
|
93
|
+
@html << char
|
94
|
+
|
95
|
+
if next_char == "\n"
|
96
|
+
@ruby << next_char
|
97
|
+
@html << next_char
|
98
|
+
@current_pos += 1
|
99
|
+
end
|
100
|
+
when "\n"
|
101
|
+
@ruby << char
|
102
|
+
@html << char
|
103
|
+
else
|
104
|
+
push_char(T.must(char))
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
sig { params(char: String).void }
|
109
|
+
def push_char(char)
|
110
|
+
if @inside_ruby
|
111
|
+
@ruby << char
|
112
|
+
@html << " " * char.length
|
113
|
+
else
|
114
|
+
@ruby << " " * char.length
|
115
|
+
@html << char
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
sig { returns(String) }
|
120
|
+
def next_char
|
121
|
+
@source[@current_pos + 1] || ""
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -21,7 +21,7 @@ module RubyLsp
|
|
21
21
|
attr_reader :encoding
|
22
22
|
|
23
23
|
sig { returns(T::Boolean) }
|
24
|
-
attr_reader :supports_watching_files
|
24
|
+
attr_reader :supports_watching_files, :experimental_features
|
25
25
|
|
26
26
|
sig { returns(TypeInferrer) }
|
27
27
|
attr_reader :type_inferrer
|
@@ -36,9 +36,10 @@ module RubyLsp
|
|
36
36
|
@test_library = T.let("minitest", String)
|
37
37
|
@has_type_checker = T.let(true, T::Boolean)
|
38
38
|
@index = T.let(RubyIndexer::Index.new, RubyIndexer::Index)
|
39
|
-
@type_inferrer = T.let(TypeInferrer.new(@index), TypeInferrer)
|
40
39
|
@supported_formatters = T.let({}, T::Hash[String, Requests::Support::Formatter])
|
41
40
|
@supports_watching_files = T.let(false, T::Boolean)
|
41
|
+
@experimental_features = T.let(false, T::Boolean)
|
42
|
+
@type_inferrer = T.let(TypeInferrer.new(@index, @experimental_features), TypeInferrer)
|
42
43
|
end
|
43
44
|
|
44
45
|
sig { params(identifier: String, instance: Requests::Support::Formatter).void }
|
@@ -56,21 +57,48 @@ module RubyLsp
|
|
56
57
|
@linters.filter_map { |name| @supported_formatters[name] }
|
57
58
|
end
|
58
59
|
|
59
|
-
|
60
|
+
# Applies the options provided by the editor and returns an array of notifications to send back to the client
|
61
|
+
sig { params(options: T::Hash[Symbol, T.untyped]).returns(T::Array[Notification]) }
|
60
62
|
def apply_options(options)
|
63
|
+
notifications = []
|
61
64
|
direct_dependencies = gather_direct_dependencies
|
62
65
|
all_dependencies = gather_direct_and_indirect_dependencies
|
63
66
|
workspace_uri = options.dig(:workspaceFolders, 0, :uri)
|
64
67
|
@workspace_uri = URI(workspace_uri) if workspace_uri
|
65
68
|
|
66
69
|
specified_formatter = options.dig(:initializationOptions, :formatter)
|
67
|
-
|
68
|
-
|
70
|
+
|
71
|
+
if specified_formatter
|
72
|
+
@formatter = specified_formatter
|
73
|
+
|
74
|
+
if specified_formatter != "auto"
|
75
|
+
notifications << Notification.window_log_message("Using formatter specified by user: #{@formatter}")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
if @formatter == "auto"
|
80
|
+
@formatter = detect_formatter(direct_dependencies, all_dependencies)
|
81
|
+
notifications << Notification.window_log_message("Auto detected formatter: #{@formatter}")
|
82
|
+
end
|
69
83
|
|
70
84
|
specified_linters = options.dig(:initializationOptions, :linters)
|
71
|
-
@linters = specified_linters || detect_linters(direct_dependencies)
|
85
|
+
@linters = specified_linters || detect_linters(direct_dependencies, all_dependencies)
|
86
|
+
|
87
|
+
notifications << if specified_linters
|
88
|
+
Notification.window_log_message("Using linters specified by user: #{@linters.join(", ")}")
|
89
|
+
else
|
90
|
+
Notification.window_log_message("Auto detected linters: #{@linters.join(", ")}")
|
91
|
+
end
|
92
|
+
|
72
93
|
@test_library = detect_test_library(direct_dependencies)
|
94
|
+
notifications << Notification.window_log_message("Detected test library: #{@test_library}")
|
95
|
+
|
73
96
|
@has_type_checker = detect_typechecker(direct_dependencies)
|
97
|
+
if @has_type_checker
|
98
|
+
notifications << Notification.window_log_message(
|
99
|
+
"Ruby LSP detected this is a Sorbet project and will defer to the Sorbet LSP for some functionality",
|
100
|
+
)
|
101
|
+
end
|
74
102
|
|
75
103
|
encodings = options.dig(:capabilities, :general, :positionEncodings)
|
76
104
|
@encoding = if !encodings || encodings.empty?
|
@@ -87,6 +115,11 @@ module RubyLsp
|
|
87
115
|
if file_watching_caps&.dig(:dynamicRegistration) && file_watching_caps&.dig(:relativePatternSupport)
|
88
116
|
@supports_watching_files = true
|
89
117
|
end
|
118
|
+
|
119
|
+
@experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
|
120
|
+
@type_inferrer.experimental_features = @experimental_features
|
121
|
+
|
122
|
+
notifications
|
90
123
|
end
|
91
124
|
|
92
125
|
sig { returns(String) }
|
@@ -124,10 +157,14 @@ module RubyLsp
|
|
124
157
|
|
125
158
|
# Try to detect if there are linters in the project's dependencies. For auto-detection, we always only consider a
|
126
159
|
# single linter. To have multiple linters running, the user must configure them manually
|
127
|
-
sig { params(dependencies: T::Array[String]).returns(T::Array[String]) }
|
128
|
-
def detect_linters(dependencies)
|
160
|
+
sig { params(dependencies: T::Array[String], all_dependencies: T::Array[String]).returns(T::Array[String]) }
|
161
|
+
def detect_linters(dependencies, all_dependencies)
|
129
162
|
linters = []
|
130
|
-
|
163
|
+
|
164
|
+
if dependencies.any?(/^rubocop/) || (all_dependencies.include?("rubocop") && dot_rubocop_yml_present)
|
165
|
+
linters << "rubocop"
|
166
|
+
end
|
167
|
+
|
131
168
|
linters
|
132
169
|
end
|
133
170
|
|
@@ -155,16 +192,7 @@ module RubyLsp
|
|
155
192
|
def detect_typechecker(dependencies)
|
156
193
|
return false if ENV["RUBY_LSP_BYPASS_TYPECHECKER"]
|
157
194
|
|
158
|
-
|
159
|
-
ruby_lsp_env_is_test = (ENV["RUBY_LSP_ENV"] == "test")
|
160
|
-
Bundler.with_original_env do
|
161
|
-
sorbet_static_detected = dependencies.any?(/^sorbet-static/)
|
162
|
-
# Don't show message while running tests, since it's noisy
|
163
|
-
if sorbet_static_detected && !ruby_lsp_env_is_test
|
164
|
-
$stderr.puts("Ruby LSP detected this is a Sorbet project so will defer to Sorbet LSP for some functionality")
|
165
|
-
end
|
166
|
-
sorbet_static_detected
|
167
|
-
end
|
195
|
+
dependencies.any?(/^sorbet-static/)
|
168
196
|
rescue Bundler::GemfileNotFound
|
169
197
|
false
|
170
198
|
end
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -15,6 +15,7 @@ Bundler.ui.level = :silent
|
|
15
15
|
require "uri"
|
16
16
|
require "cgi"
|
17
17
|
require "set"
|
18
|
+
require "strscan"
|
18
19
|
require "prism"
|
19
20
|
require "prism/visitor"
|
20
21
|
require "language_server-protocol"
|
@@ -34,6 +35,7 @@ require "ruby_lsp/response_builders"
|
|
34
35
|
require "ruby_lsp/node_context"
|
35
36
|
require "ruby_lsp/document"
|
36
37
|
require "ruby_lsp/ruby_document"
|
38
|
+
require "ruby_lsp/erb_document"
|
37
39
|
require "ruby_lsp/store"
|
38
40
|
require "ruby_lsp/addon"
|
39
41
|
require "ruby_lsp/requests/support/rubocop_runner"
|