solargraph 0.19.1 → 0.20.0
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/lib/solargraph.rb +1 -0
- data/lib/solargraph/api_map.rb +29 -4
- data/lib/solargraph/api_map/probe.rb +3 -3
- data/lib/solargraph/diagnostics.rb +8 -0
- data/lib/solargraph/diagnostics/base.rb +12 -0
- data/lib/solargraph/diagnostics/require_not_found.rb +23 -0
- data/lib/solargraph/diagnostics/rubocop.rb +17 -6
- data/lib/solargraph/diagnostics/severities.rb +13 -0
- data/lib/solargraph/language_server.rb +5 -4
- data/lib/solargraph/language_server/host.rb +113 -19
- data/lib/solargraph/language_server/message.rb +2 -0
- data/lib/solargraph/language_server/message/base.rb +1 -1
- data/lib/solargraph/language_server/message/extended.rb +2 -0
- data/lib/solargraph/language_server/message/extended/check_gem_version.rb +38 -0
- data/lib/solargraph/language_server/message/extended/document_gems.rb +23 -0
- data/lib/solargraph/language_server/message/text_document/on_type_formatting.rb +6 -7
- data/lib/solargraph/language_server/request.rb +14 -0
- data/lib/solargraph/library.rb +37 -3
- data/lib/solargraph/page.rb +12 -13
- data/lib/solargraph/pin/helper.rb +9 -3
- data/lib/solargraph/pin/localized.rb +9 -2
- data/lib/solargraph/pin/yard_object.rb +2 -3
- data/lib/solargraph/shell.rb +6 -0
- data/lib/solargraph/source.rb +5 -2
- data/lib/solargraph/source/fragment.rb +8 -0
- data/lib/solargraph/source/mapper.rb +6 -11
- data/lib/solargraph/version.rb +1 -1
- data/lib/solargraph/workspace.rb +33 -0
- data/lib/solargraph/workspace/config.rb +12 -0
- data/lib/solargraph/yard_map.rb +27 -87
- data/lib/yard-solargraph.rb +1 -0
- metadata +11 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e1a1a75c9706d5d54ca2359b21bf336b9980bce
|
4
|
+
data.tar.gz: 618ae6ec9b01ddd2ee7656a34c787cc6b26bfc0b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7ab817f132710bdb79f93773937cb8280dd764b07e7bd29f4b33064e097690a192afc219f6bbcf69b00d3ca1257cb8a6e90db91c9c8ee3109fa522f6ea054474
|
7
|
+
data.tar.gz: ccb208a618f8c891c6bfd102e45bd65a31307abc87bb51cd738ed64582eb0e1a45adbc4ba7e7248de64b5cc6760dfa68370eb6f883c63a18c340a2657e38a4b3
|
data/lib/solargraph.rb
CHANGED
data/lib/solargraph/api_map.rb
CHANGED
@@ -45,13 +45,17 @@ module Solargraph
|
|
45
45
|
store.pins
|
46
46
|
end
|
47
47
|
|
48
|
+
def domains
|
49
|
+
@domains ||= []
|
50
|
+
end
|
51
|
+
|
48
52
|
# An array of required paths in the workspace.
|
49
53
|
#
|
50
54
|
# @return [Array<String>]
|
51
55
|
def required
|
52
56
|
result = []
|
53
57
|
@sources.each do |s|
|
54
|
-
result.concat s.required
|
58
|
+
result.concat s.required.map(&:name)
|
55
59
|
end
|
56
60
|
result.uniq
|
57
61
|
end
|
@@ -84,6 +88,10 @@ module Solargraph
|
|
84
88
|
# @return [Solargraph::Source]
|
85
89
|
def virtualize source
|
86
90
|
store.remove @virtual_source unless @virtual_source.nil?
|
91
|
+
domains.clear
|
92
|
+
domains.concat workspace.config.domains
|
93
|
+
domains.concat source.domains unless source.nil?
|
94
|
+
domains.uniq!
|
87
95
|
if workspace.has_source?(source)
|
88
96
|
@sources = workspace.sources
|
89
97
|
@virtual_source = nil
|
@@ -158,10 +166,10 @@ module Solargraph
|
|
158
166
|
# True if the namespace exists.
|
159
167
|
#
|
160
168
|
# @param name [String] The namespace to match
|
161
|
-
# @param
|
169
|
+
# @param context [String] The context to search
|
162
170
|
# @return [Boolean]
|
163
|
-
def namespace_exists? name,
|
164
|
-
!qualify(name,
|
171
|
+
def namespace_exists? name, context = ''
|
172
|
+
!qualify(name, context).nil?
|
165
173
|
end
|
166
174
|
|
167
175
|
# Get suggestions for constants in the specified namespace. The result
|
@@ -241,6 +249,10 @@ module Solargraph
|
|
241
249
|
result = []
|
242
250
|
skip = []
|
243
251
|
if fqns == ''
|
252
|
+
domains.each do |domain|
|
253
|
+
namespace, scope = extract_namespace_and_scope(domain)
|
254
|
+
result.concat inner_get_methods(namespace, scope, [:public], deep, skip)
|
255
|
+
end
|
244
256
|
result.concat inner_get_methods(fqns, :class, visibility, deep, skip)
|
245
257
|
result.concat inner_get_methods(fqns, :instance, visibility, deep, skip)
|
246
258
|
result.concat inner_get_methods('Kernel', :instance, visibility, deep, skip)
|
@@ -318,6 +330,12 @@ module Solargraph
|
|
318
330
|
probe.infer_signature_type fragment.whole_signature, fragment.named_path, fragment.locals
|
319
331
|
end
|
320
332
|
|
333
|
+
# Get an array of pins that describe the method being called by the
|
334
|
+
# argument list where the fragment is located. This is useful for queries
|
335
|
+
# that need to know what parameters the current method expects to receive.
|
336
|
+
#
|
337
|
+
# If the fragment is not inside an argument list, return an empty array.
|
338
|
+
#
|
321
339
|
# @param fragment [Solargraph::Source::Fragment]
|
322
340
|
# @return [Array<Solargraph::Pin::Base>]
|
323
341
|
def signify fragment
|
@@ -373,6 +391,9 @@ module Solargraph
|
|
373
391
|
docs
|
374
392
|
end
|
375
393
|
|
394
|
+
# Get an array of all symbols in the workspace that match the query.
|
395
|
+
#
|
396
|
+
# @return [Array<Pin::Base>]
|
376
397
|
def query_symbols query
|
377
398
|
result = []
|
378
399
|
@sources.each do |s|
|
@@ -473,6 +494,10 @@ module Solargraph
|
|
473
494
|
result
|
474
495
|
end
|
475
496
|
|
497
|
+
# Require extensions for the experimental plugin architecture. Any
|
498
|
+
# installed gem with a name that starts with "solargraph-" is considered
|
499
|
+
# an extension.
|
500
|
+
#
|
476
501
|
def require_extensions
|
477
502
|
Gem::Specification.all_names.select{|n| n.match(/^solargraph\-[a-z0-9_\-]*?\-ext\-[0-9\.]*$/)}.each do |n|
|
478
503
|
STDERR.puts "Loading extension #{n}"
|
@@ -66,6 +66,7 @@ module Solargraph
|
|
66
66
|
return [] if word.empty?
|
67
67
|
lvars = locals.select{|pin| pin.name == word}
|
68
68
|
return lvars unless lvars.empty?
|
69
|
+
return api_map.get_global_variable_pins.select{|pin| pin.name == word} if word.start_with?('$')
|
69
70
|
namespace, scope = extract_namespace_and_scope_from_pin(context_pin)
|
70
71
|
return api_map.pins.select{|pin| word_matches_context?(word, namespace, scope, pin)} if variable_name?(word)
|
71
72
|
result = []
|
@@ -139,13 +140,12 @@ module Solargraph
|
|
139
140
|
end
|
140
141
|
|
141
142
|
# Extract a namespace and a scope from a pin. For now, the pin must
|
142
|
-
# be either a method or a
|
143
|
-
# blocks at some point.
|
143
|
+
# be either a namespace, a method, or a block.
|
144
144
|
#
|
145
145
|
# @return [Array] The namespace (String) and scope (Symbol).
|
146
146
|
def extract_namespace_and_scope_from_pin pin
|
147
147
|
return [pin.namespace, pin.scope] if pin.kind == Pin::METHOD
|
148
|
-
return [pin.
|
148
|
+
return [pin.path, :class] if pin.kind == Pin::NAMESPACE
|
149
149
|
# @todo Is :class appropriate for blocks?
|
150
150
|
return [pin.namespace, :class] if pin.kind == Pin::BLOCK
|
151
151
|
raise "Unable to extract namespace and scope from #{pin.path}"
|
@@ -1,5 +1,13 @@
|
|
1
1
|
module Solargraph
|
2
2
|
module Diagnostics
|
3
|
+
autoload :Base, 'solargraph/diagnostics/base'
|
4
|
+
autoload :Severities, 'solargraph/diagnostics/severities'
|
3
5
|
autoload :Rubocop, 'solargraph/diagnostics/rubocop'
|
6
|
+
autoload :RequireNotFound, 'solargraph/diagnostics/require_not_found'
|
7
|
+
|
8
|
+
REPORTERS = {
|
9
|
+
'rubocop' => Rubocop,
|
10
|
+
'require_not_found' => RequireNotFound
|
11
|
+
}
|
4
12
|
end
|
5
13
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Solargraph
|
2
|
+
module Diagnostics
|
3
|
+
class RequireNotFound < Base
|
4
|
+
def diagnose source, api_map
|
5
|
+
result = []
|
6
|
+
refs = {}
|
7
|
+
source.requires.each do |ref|
|
8
|
+
refs[ref.name] = ref
|
9
|
+
end
|
10
|
+
api_map.yard_map.unresolved_requires.each do |r|
|
11
|
+
next unless refs.has_key?(r)
|
12
|
+
result.push(
|
13
|
+
range: refs[r].location.range.to_hash,
|
14
|
+
severity: Diagnostics::Severities::WARNING,
|
15
|
+
source: 'Solargraph',
|
16
|
+
message: "Required path #{r} could not be resolved."
|
17
|
+
)
|
18
|
+
end
|
19
|
+
result
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -2,28 +2,39 @@ require 'open3'
|
|
2
2
|
require 'shellwords'
|
3
3
|
|
4
4
|
module Solargraph
|
5
|
-
|
6
5
|
module Diagnostics
|
7
|
-
class Rubocop
|
8
|
-
|
6
|
+
class Rubocop < Base
|
7
|
+
# The rubocop command
|
8
|
+
#
|
9
|
+
# @return [String]
|
10
|
+
attr_reader :command
|
11
|
+
|
12
|
+
def initialize(command = 'rubocop')
|
13
|
+
@command = command
|
9
14
|
end
|
10
15
|
|
11
16
|
# @return [Array<Hash>]
|
12
|
-
def diagnose
|
17
|
+
def diagnose source, api_map
|
13
18
|
begin
|
14
|
-
|
19
|
+
text = source.code
|
20
|
+
filename = source.filename
|
21
|
+
raise DiagnosticsError, 'No command specified' if command.nil? or command.empty?
|
22
|
+
cmd = "#{Shellwords.escape(command)} -f j -s #{Shellwords.escape(filename)}"
|
15
23
|
o, e, s = Open3.capture3(cmd, stdin_data: text)
|
16
|
-
raise DiagnosticsError, "
|
24
|
+
raise DiagnosticsError, "Command '#{command}' is not available (gem exception)" if e.include?('Gem::Exception')
|
17
25
|
raise DiagnosticsError, "RuboCop returned empty data" if o.empty?
|
18
26
|
make_array JSON.parse(o)
|
19
27
|
rescue JSON::ParserError
|
20
28
|
raise DiagnosticsError, 'RuboCop returned invalid data'
|
29
|
+
rescue Errno::ENOENT
|
30
|
+
raise DiagnosticsError, "Command '#{command}' is not available"
|
21
31
|
end
|
22
32
|
end
|
23
33
|
|
24
34
|
private
|
25
35
|
|
26
36
|
def make_array resp
|
37
|
+
# Conversion of RuboCop severity names to LSP constants
|
27
38
|
severities = {
|
28
39
|
'refactor' => 4,
|
29
40
|
'convention' => 3,
|
@@ -4,10 +4,11 @@ require 'solargraph/language_server/symbol_kinds'
|
|
4
4
|
|
5
5
|
module Solargraph
|
6
6
|
module LanguageServer
|
7
|
-
autoload :Host,
|
8
|
-
autoload :Message,
|
9
|
-
autoload :
|
10
|
-
autoload :UriHelpers, 'solargraph/language_server/uri_helpers'
|
7
|
+
autoload :Host, 'solargraph/language_server/host'
|
8
|
+
autoload :Message, 'solargraph/language_server/message'
|
9
|
+
autoload :UriHelpers, 'solargraph/language_server/uri_helpers'
|
11
10
|
autoload :MessageTypes, 'solargraph/language_server/message_types'
|
11
|
+
autoload :Request, 'solargraph/language_server/request'
|
12
|
+
autoload :Transport, 'solargraph/language_server/transport'
|
12
13
|
end
|
13
14
|
end
|
@@ -4,7 +4,8 @@ require 'set'
|
|
4
4
|
module Solargraph
|
5
5
|
module LanguageServer
|
6
6
|
# The language server protocol's data provider. Hosts are responsible for
|
7
|
-
# querying the library and processing messages.
|
7
|
+
# querying the library and processing messages. They also provide thread
|
8
|
+
# safety for multi-threaded transports.
|
8
9
|
#
|
9
10
|
class Host
|
10
11
|
include Solargraph::LanguageServer::UriHelpers
|
@@ -18,10 +19,13 @@ module Solargraph
|
|
18
19
|
@cancel = []
|
19
20
|
@buffer = ''
|
20
21
|
@stopped = false
|
22
|
+
@next_request_id = 0
|
21
23
|
start_change_thread
|
22
24
|
start_diagnostics_thread
|
23
25
|
end
|
24
26
|
|
27
|
+
# Update the configuration options with the provided hash.
|
28
|
+
#
|
25
29
|
# @param update [Hash]
|
26
30
|
def configure update
|
27
31
|
options.merge! update unless update.nil?
|
@@ -46,18 +50,36 @@ module Solargraph
|
|
46
50
|
@cancel_semaphore.synchronize { @cancel.delete id }
|
47
51
|
end
|
48
52
|
|
53
|
+
# Start processing a request from the client. After the message is
|
54
|
+
# processed, the transport is responsible for sending the response.
|
55
|
+
#
|
56
|
+
# @param request [Hash] The contents of the message.
|
57
|
+
# @return [Solargraph::LanguageServer::Message] The message handler.
|
49
58
|
def start request
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
59
|
+
if request['method']
|
60
|
+
message = Message.select(request['method']).new(self, request)
|
61
|
+
begin
|
62
|
+
message.process
|
63
|
+
rescue Exception => e
|
64
|
+
STDERR.puts e.message
|
65
|
+
STDERR.puts e.backtrace
|
66
|
+
message.set_error Solargraph::LanguageServer::ErrorCodes::INTERNAL_ERROR, "[#{e.class}] #{e.message}"
|
67
|
+
end
|
68
|
+
message
|
69
|
+
elsif request['id']
|
70
|
+
# @todo What if the id is invalid?
|
71
|
+
requests[request['id']].process(request['result'])
|
72
|
+
requests.delete request['id']
|
73
|
+
else
|
74
|
+
STDERR.puts "Invalid message received."
|
57
75
|
end
|
58
|
-
message
|
59
76
|
end
|
60
77
|
|
78
|
+
# Respond to a notification that a file was created in the workspace.
|
79
|
+
# The library will determine whether the file should be added to the
|
80
|
+
# workspace; see Solargraph::Library#create_from_disk.
|
81
|
+
#
|
82
|
+
# @param uri [String] The file uri.
|
61
83
|
def create uri
|
62
84
|
filename = uri_to_file(uri)
|
63
85
|
@change_semaphore.synchronize do
|
@@ -65,6 +87,9 @@ module Solargraph
|
|
65
87
|
end
|
66
88
|
end
|
67
89
|
|
90
|
+
# Delete the specified file from the library.
|
91
|
+
#
|
92
|
+
# @param uri [String] The file uri.
|
68
93
|
def delete uri
|
69
94
|
@change_semaphore.synchronize do
|
70
95
|
filename = uri_to_file(uri)
|
@@ -72,6 +97,11 @@ module Solargraph
|
|
72
97
|
end
|
73
98
|
end
|
74
99
|
|
100
|
+
# Open the specified file in the library.
|
101
|
+
#
|
102
|
+
# @param uri [String] The file uri.
|
103
|
+
# @param text [String] The contents of the file.
|
104
|
+
# @param version [Integer] A version number.
|
75
105
|
def open uri, text, version
|
76
106
|
@change_semaphore.synchronize do
|
77
107
|
library.open uri_to_file(uri), text, version
|
@@ -79,6 +109,9 @@ module Solargraph
|
|
79
109
|
end
|
80
110
|
end
|
81
111
|
|
112
|
+
# True if the specified file is currently open in the library.
|
113
|
+
#
|
114
|
+
# @return [Boolean]
|
82
115
|
def open? uri
|
83
116
|
result = nil
|
84
117
|
@change_semaphore.synchronize do
|
@@ -90,6 +123,7 @@ module Solargraph
|
|
90
123
|
def close uri
|
91
124
|
@change_semaphore.synchronize do
|
92
125
|
library.close uri_to_file(uri)
|
126
|
+
@diagnostics_queue.push uri
|
93
127
|
end
|
94
128
|
end
|
95
129
|
|
@@ -124,12 +158,18 @@ module Solargraph
|
|
124
158
|
end
|
125
159
|
end
|
126
160
|
|
161
|
+
# Queue a message to be sent to the client.
|
162
|
+
#
|
163
|
+
# @param message [String] The message to send.
|
127
164
|
def queue message
|
128
165
|
@buffer_semaphore.synchronize do
|
129
166
|
@buffer += message
|
130
167
|
end
|
131
168
|
end
|
132
169
|
|
170
|
+
# Clear the message buffer and return the most recent data.
|
171
|
+
#
|
172
|
+
# @return [String] The most recent data or an empty string.
|
133
173
|
def flush
|
134
174
|
tmp = nil
|
135
175
|
@buffer_semaphore.synchronize do
|
@@ -139,6 +179,8 @@ module Solargraph
|
|
139
179
|
tmp
|
140
180
|
end
|
141
181
|
|
182
|
+
# Prepare a library for the specified directory.
|
183
|
+
#
|
142
184
|
# @param directory [String]
|
143
185
|
def prepare directory
|
144
186
|
path = nil
|
@@ -156,6 +198,10 @@ module Solargraph
|
|
156
198
|
end
|
157
199
|
end
|
158
200
|
|
201
|
+
# Send a notification to the client.
|
202
|
+
#
|
203
|
+
# @param method [String] The message method
|
204
|
+
# @param params [Hash] The method parameters
|
159
205
|
def send_notification method, params
|
160
206
|
response = {
|
161
207
|
jsonrpc: "2.0",
|
@@ -167,6 +213,29 @@ module Solargraph
|
|
167
213
|
queue envelope
|
168
214
|
end
|
169
215
|
|
216
|
+
# Send a request to the client and execute the provided block to process
|
217
|
+
# the response.
|
218
|
+
#
|
219
|
+
# @param method [String] The message method
|
220
|
+
# @param params [Hash] The method parameters
|
221
|
+
# @yieldparam [Hash] The result sent by the client
|
222
|
+
def send_request method, params, &block
|
223
|
+
message = {
|
224
|
+
jsonrpc: "2.0",
|
225
|
+
method: method,
|
226
|
+
params: params,
|
227
|
+
id: @next_request_id
|
228
|
+
}
|
229
|
+
json = message.to_json
|
230
|
+
requests[@next_request_id] = Request.new(@next_request_id, &block)
|
231
|
+
envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
|
232
|
+
queue envelope
|
233
|
+
@next_request_id += 1
|
234
|
+
end
|
235
|
+
|
236
|
+
# True if the specified file is in the process of changing.
|
237
|
+
#
|
238
|
+
# @return [Boolean]
|
170
239
|
def changing? file_uri
|
171
240
|
result = false
|
172
241
|
@change_semaphore.synchronize do
|
@@ -251,6 +320,29 @@ module Solargraph
|
|
251
320
|
library.file_symbols(uri_to_file(uri))
|
252
321
|
end
|
253
322
|
|
323
|
+
def show_message text, type = LanguageServer::MessageTypes::INFO
|
324
|
+
send_notification 'window/showMessage', {
|
325
|
+
type: type,
|
326
|
+
message: text
|
327
|
+
}
|
328
|
+
end
|
329
|
+
|
330
|
+
def show_message_request text, type, actions, &block
|
331
|
+
send_request 'window/showMessageRequest', {
|
332
|
+
type: type,
|
333
|
+
message: text,
|
334
|
+
actions: actions
|
335
|
+
}, &block
|
336
|
+
end
|
337
|
+
|
338
|
+
# Get a list of IDs for server requests that are waiting for responses
|
339
|
+
# from the client.
|
340
|
+
#
|
341
|
+
# @return [Array<Integer>]
|
342
|
+
def pending_requests
|
343
|
+
requests.keys
|
344
|
+
end
|
345
|
+
|
254
346
|
private
|
255
347
|
|
256
348
|
# @return [Solargraph::Library]
|
@@ -262,6 +354,10 @@ module Solargraph
|
|
262
354
|
@change_queue.any?{|change| change['textDocument']['uri'] == file_uri}
|
263
355
|
end
|
264
356
|
|
357
|
+
def requests
|
358
|
+
@requests ||= {}
|
359
|
+
end
|
360
|
+
|
265
361
|
def start_change_thread
|
266
362
|
Thread.new do
|
267
363
|
until stopped?
|
@@ -290,7 +386,7 @@ module Solargraph
|
|
290
386
|
next true
|
291
387
|
elsif change['textDocument']['version'] <= source.version
|
292
388
|
# @todo Is deleting outdated changes correct behavior?
|
293
|
-
STDERR.puts "Warning: outdated
|
389
|
+
STDERR.puts "Warning: outdated change to #{change['textDocument']['uri']} was ignored"
|
294
390
|
@diagnostics_queue.push change['textDocument']['uri']
|
295
391
|
next true
|
296
392
|
else
|
@@ -333,8 +429,10 @@ module Solargraph
|
|
333
429
|
end
|
334
430
|
next if current.nil? or already_changing
|
335
431
|
filename = uri_to_file(current)
|
336
|
-
text = library.read_text(filename)
|
337
|
-
results = diagnoser.diagnose text, filename
|
432
|
+
# text = library.read_text(filename)
|
433
|
+
# results = diagnoser.diagnose text, filename
|
434
|
+
# results.concat library.diagnose(filename)
|
435
|
+
results = library.diagnose(filename)
|
338
436
|
@change_semaphore.synchronize do
|
339
437
|
already_changing = (unsafe_changing?(current) or @diagnostics_queue.include?(current))
|
340
438
|
# publish_diagnostics current, resp unless already_changing
|
@@ -352,13 +450,9 @@ module Solargraph
|
|
352
450
|
type: LanguageServer::MessageTypes::ERROR,
|
353
451
|
message: "Error in diagnostics: #{e.message}"
|
354
452
|
}
|
355
|
-
rescue
|
356
|
-
STDERR.puts "
|
357
|
-
|
358
|
-
send_notification 'window/showMessage', {
|
359
|
-
type: LanguageServer::MessageTypes::ERROR,
|
360
|
-
message: "Error in diagnostics: RuboCop could not be found"
|
361
|
-
}
|
453
|
+
rescue Exception => e
|
454
|
+
STDERR.puts "#{e.message}"
|
455
|
+
STDERR.puts "#{e.backtrace}"
|
362
456
|
end
|
363
457
|
end
|
364
458
|
end
|