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.
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