solargraph 0.19.1 → 0.20.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|