solargraph 0.19.1 → 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/lib/solargraph.rb +1 -0
  3. data/lib/solargraph/api_map.rb +29 -4
  4. data/lib/solargraph/api_map/probe.rb +3 -3
  5. data/lib/solargraph/diagnostics.rb +8 -0
  6. data/lib/solargraph/diagnostics/base.rb +12 -0
  7. data/lib/solargraph/diagnostics/require_not_found.rb +23 -0
  8. data/lib/solargraph/diagnostics/rubocop.rb +17 -6
  9. data/lib/solargraph/diagnostics/severities.rb +13 -0
  10. data/lib/solargraph/language_server.rb +5 -4
  11. data/lib/solargraph/language_server/host.rb +113 -19
  12. data/lib/solargraph/language_server/message.rb +2 -0
  13. data/lib/solargraph/language_server/message/base.rb +1 -1
  14. data/lib/solargraph/language_server/message/extended.rb +2 -0
  15. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +38 -0
  16. data/lib/solargraph/language_server/message/extended/document_gems.rb +23 -0
  17. data/lib/solargraph/language_server/message/text_document/on_type_formatting.rb +6 -7
  18. data/lib/solargraph/language_server/request.rb +14 -0
  19. data/lib/solargraph/library.rb +37 -3
  20. data/lib/solargraph/page.rb +12 -13
  21. data/lib/solargraph/pin/helper.rb +9 -3
  22. data/lib/solargraph/pin/localized.rb +9 -2
  23. data/lib/solargraph/pin/yard_object.rb +2 -3
  24. data/lib/solargraph/shell.rb +6 -0
  25. data/lib/solargraph/source.rb +5 -2
  26. data/lib/solargraph/source/fragment.rb +8 -0
  27. data/lib/solargraph/source/mapper.rb +6 -11
  28. data/lib/solargraph/version.rb +1 -1
  29. data/lib/solargraph/workspace.rb +33 -0
  30. data/lib/solargraph/workspace/config.rb +12 -0
  31. data/lib/solargraph/yard_map.rb +27 -87
  32. data/lib/yard-solargraph.rb +1 -0
  33. metadata +11 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 641234ab31a7a58d48d4f9ac92b50b7b7a648a48
4
- data.tar.gz: 964eb2b062f2811e3365c3de84320996230f5ee3
3
+ metadata.gz: 0e1a1a75c9706d5d54ca2359b21bf336b9980bce
4
+ data.tar.gz: 618ae6ec9b01ddd2ee7656a34c787cc6b26bfc0b
5
5
  SHA512:
6
- metadata.gz: 6042457038382e3ee2cf52af10477b25fe4c43c8dd45925aaf21bbc1c762bdfa2e39aeaf02ef7acdedf41ee5134992f887c1e63ff2a0437d10a7f4a6e11bc2e8
7
- data.tar.gz: ff40baf86a70fb9e5b6359ac175610123c307f16011d8d5a2f694df9ea49e16b4a82c40cb0cec01ca3b9275399b91a9f815cbf91ecf8d5f89b05f8caec8ad68a
6
+ metadata.gz: 7ab817f132710bdb79f93773937cb8280dd764b07e7bd29f4b33064e097690a192afc219f6bbcf69b00d3ca1257cb8a6e90db91c9c8ee3109fa522f6ea054474
7
+ data.tar.gz: ccb208a618f8c891c6bfd102e45bd65a31307abc87bb51cd738ed64582eb0e1a45adbc4ba7e7248de64b5cc6760dfa68370eb6f883c63a18c340a2657e38a4b3
data/lib/solargraph.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'solargraph/version'
2
2
  require 'rubygems/package'
3
3
  require 'yard-solargraph'
4
+ require 'sinatra'
4
5
 
5
6
  module Solargraph
6
7
  class InvalidOffsetError < RangeError; end
@@ -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 root [String] The context to search
169
+ # @param context [String] The context to search
162
170
  # @return [Boolean]
163
- def namespace_exists? name, root = ''
164
- !qualify(name, root).nil?
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 namespace. It probably makes sense to support
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.namespace, :class] if pin.kind == Pin::NAMESPACE
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,12 @@
1
+ module Solargraph
2
+ module Diagnostics
3
+ class Base
4
+ # @param source [Solargraph::Source]
5
+ # @param source [Solargraph::ApiMap]
6
+ #
7
+ # @return [Array<Hash>]
8
+ def diagnose source, api_map
9
+ end
10
+ end
11
+ end
12
+ 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
- def initialize
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 text, filename
17
+ def diagnose source, api_map
13
18
  begin
14
- cmd = "rubocop -f j -s #{Shellwords.escape(filename)}"
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, "RuboCop is not available" if e.include?('Gem::Exception')
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,
@@ -0,0 +1,13 @@
1
+ module Solargraph
2
+ module Diagnostics
3
+ # These severity constants match the DiagnosticSeverity constants in the
4
+ # language server protocol.
5
+ #
6
+ module Severities
7
+ ERROR = 1
8
+ WARNING = 2
9
+ INFORMATION = 3
10
+ HINT = 4
11
+ end
12
+ end
13
+ end
@@ -4,10 +4,11 @@ require 'solargraph/language_server/symbol_kinds'
4
4
 
5
5
  module Solargraph
6
6
  module LanguageServer
7
- autoload :Host, 'solargraph/language_server/host'
8
- autoload :Message, 'solargraph/language_server/message'
9
- autoload :Transport, 'solargraph/language_server/transport'
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
- message = Message.select(request['method']).new(self, request)
51
- begin
52
- message.process
53
- rescue Exception => e
54
- STDERR.puts e.message
55
- STDERR.puts e.backtrace
56
- message.set_error Solargraph::LanguageServer::ErrorCodes::INTERNAL_ERROR, "[#{e.class}] #{e.message}"
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 to change to #{change['textDocument']['uri']} was ignored"
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 Errno::ENOENT => e
356
- STDERR.puts "Error in diagnostics: RuboCop could not be found"
357
- options['diagnostics'] = false
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