solargraph 0.42.4 → 0.43.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -1
  3. data/README.md +2 -1
  4. data/lib/solargraph/api_map/store.rb +17 -17
  5. data/lib/solargraph/api_map.rb +1 -1
  6. data/lib/solargraph/complex_type/type_methods.rb +6 -3
  7. data/lib/solargraph/complex_type.rb +4 -0
  8. data/lib/solargraph/documentor.rb +10 -12
  9. data/lib/solargraph/language_server/host/dispatch.rb +2 -3
  10. data/lib/solargraph/language_server/host/message_worker.rb +59 -0
  11. data/lib/solargraph/language_server/host.rb +47 -34
  12. data/lib/solargraph/language_server/message/base.rb +6 -2
  13. data/lib/solargraph/language_server/message/text_document/completion.rb +2 -0
  14. data/lib/solargraph/language_server/transport/adapter.rb +1 -2
  15. data/lib/solargraph/library.rb +8 -3
  16. data/lib/solargraph/parser/comment_ripper.rb +1 -1
  17. data/lib/solargraph/parser/legacy/class_methods.rb +25 -0
  18. data/lib/solargraph/parser/legacy/node_chainer.rb +14 -1
  19. data/lib/solargraph/parser/legacy/node_methods.rb +9 -2
  20. data/lib/solargraph/parser/rubyvm/node_chainer.rb +22 -1
  21. data/lib/solargraph/parser/rubyvm/node_methods.rb +5 -0
  22. data/lib/solargraph/pin/base.rb +1 -1
  23. data/lib/solargraph/pin/namespace.rb +8 -2
  24. data/lib/solargraph/source/chain/hash.rb +28 -0
  25. data/lib/solargraph/source/chain.rb +1 -0
  26. data/lib/solargraph/source_map/clip.rb +1 -1
  27. data/lib/solargraph/source_map/mapper.rb +2 -0
  28. data/lib/solargraph/type_checker.rb +11 -4
  29. data/lib/solargraph/version.rb +1 -1
  30. data/lib/solargraph/views/_method.erb +1 -1
  31. data/lib/solargraph/workspace.rb +14 -10
  32. data/lib/solargraph/yard_map.rb +5 -1
  33. data/lib/solargraph.rb +1 -0
  34. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 28ad96860cdfaaa7ce608149d111164649664a86e13567c63efb0218cac98bff
4
- data.tar.gz: 12f96ea8a87a97a98e0f91774bff44ff3c736fe5268cbdb959cfbf0ce70c53b6
3
+ metadata.gz: ed5d2e054ff8eca78f0b4f2294eecd1018cb32f53bd43c241e803edefeb4fce1
4
+ data.tar.gz: c8161d2b732b03024d592a6db89a07c4cf27588d5a8b8d41c6cfcd63871c6796
5
5
  SHA512:
6
- metadata.gz: '00259fb37ec37b1090797678288f75fc9e1fd26db368d11f6e98f6c89b78745141d416a6a40cc507ce1f032056318c9f67946d9a6d9e17646aee0c9b54a1fd5d'
7
- data.tar.gz: 264c5b2e39c4f946f9e375b7d5ef17f97d42c7b6343aa632f1c0319e1a2c6b2b2d73dc50f31b7b863b55b814e0b089cfae63137e8548927e2e0935c4e3243402
6
+ metadata.gz: 6b310e476d1f7434a5da52adeda1212994b5cfa9907a60475a1dbd230e2bdbf9619a5a16420524fb0542e13dd527010f437e032f39ccac3596543bc96c4b1c24
7
+ data.tar.gz: 65bd2f40560050fc92f79bb4450a122f61e9b5a80bf58703f8317bd86d50058d6834b07b02d699b9804ab803c228f77441a32e37049d95ea4364704b916cf908
data/CHANGELOG.md CHANGED
@@ -1,4 +1,27 @@
1
- ## 0.42.4
1
+ ## 0.43.3 - September 25, 2021
2
+ - Avoid Dir.chdir in Bundler processes (#481)
3
+ - Check stdlib for gems with empty yardocs
4
+ - Library#maybe_map checks for source in workspace
5
+
6
+ ## 0.43.2 - September 23, 2021
7
+ - Synchronize server requests (#461)
8
+
9
+ ## 0.43.1 - September 20, 2021
10
+ - Complete nested namespaces in open gates
11
+ - SourceMap::Mapper reports filename for encoding errors (#474)
12
+ - Handle request on a specific thread, and cancel completion when there has newer completion request (#459)
13
+ - Fix namespace links generated by views/_method.erb (#472)
14
+ - Source handles long squiggly heredocs (#460)
15
+
16
+ ## 0.43.0 - July 25, 2021
17
+ - Correct arity checks when restarg precedes arg (#418)
18
+ - Improve the performance of catalog by 4 times (#457)
19
+ - Type checker validates duck type variables and params (#453)
20
+ - Kernel#raise exception type checker
21
+ - Pin::Base#inspect includes path
22
+ - Fix arity with combined restargs and kwrestargs (#396)
23
+
24
+ ## 0.42.4 - July 11, 2021
2
25
  - Yardoc cache handling
3
26
  - Fix required_paths when gemspec is used (#451)
4
27
  - fix: yard stdout may break language client (#454)
data/README.md CHANGED
@@ -34,7 +34,8 @@ Plug-ins and extensions are available for the following editors:
34
34
  * GitHub: https://github.com/autozimu/LanguageClient-neovim
35
35
 
36
36
  * **Emacs**
37
- * GitHub: https://github.com/guskovd/emacs-solargraph
37
+ * GitHub: `eglot.el`, https://github.com/joaotavora/eglot
38
+ * GitHub: `lsp-mode.el`, https://github.com/emacs-lsp/lsp-mode
38
39
 
39
40
  * **Eclipse**
40
41
  * Plugin: https://marketplace.eclipse.org/content/ruby-solargraph
@@ -5,10 +5,10 @@ require 'set'
5
5
  module Solargraph
6
6
  class ApiMap
7
7
  class Store
8
- # @return [Array<Solargraph::Pin::Base>]
8
+ # @return [Enumerable<Solargraph::Pin::Base>]
9
9
  attr_reader :pins
10
10
 
11
- # @param pins [Array<Solargraph::Pin::Base>]
11
+ # @param pins [Enumerable<Solargraph::Pin::Base>]
12
12
  def initialize pins = []
13
13
  @pins = pins
14
14
  index
@@ -16,7 +16,7 @@ module Solargraph
16
16
 
17
17
  # @param fqns [String]
18
18
  # @param visibility [Array<Symbol>]
19
- # @return [Array<Solargraph::Pin::Base>]
19
+ # @return [Enumerable<Solargraph::Pin::Base>]
20
20
  def get_constants fqns, visibility = [:public]
21
21
  namespace_children(fqns).select { |pin|
22
22
  !pin.name.empty? && (pin.is_a?(Pin::Namespace) || pin.is_a?(Pin::Constant)) && visibility.include?(pin.visibility)
@@ -26,7 +26,7 @@ module Solargraph
26
26
  # @param fqns [String]
27
27
  # @param scope [Symbol]
28
28
  # @param visibility [Array<Symbol>]
29
- # @return [Array<Solargraph::Pin::Base>]
29
+ # @return [Enumerable<Solargraph::Pin::Base>]
30
30
  def get_methods fqns, scope: :instance, visibility: [:public]
31
31
  namespace_children(fqns).select do |pin|
32
32
  pin.is_a?(Pin::Method) && pin.scope == scope && visibility.include?(pin.visibility)
@@ -61,14 +61,14 @@ module Solargraph
61
61
  end
62
62
 
63
63
  # @param path [String]
64
- # @return [Array<Solargraph::Pin::Base>]
64
+ # @return [Enumerable<Solargraph::Pin::Base>]
65
65
  def get_path_pins path
66
66
  path_pin_hash[path] || []
67
67
  end
68
68
 
69
69
  # @param fqns [String]
70
70
  # @param scope [Symbol] :class or :instance
71
- # @return [Array<Solargraph::Pin::Base>]
71
+ # @return [Enumerable<Solargraph::Pin::Base>]
72
72
  def get_instance_variables(fqns, scope = :instance)
73
73
  all_instance_variables.select { |pin|
74
74
  pin.binder.namespace == fqns && pin.binder.scope == scope
@@ -76,12 +76,12 @@ module Solargraph
76
76
  end
77
77
 
78
78
  # @param fqns [String]
79
- # @return [Array<Solargraph::Pin::Base>]
79
+ # @return [Enumerable<Solargraph::Pin::Base>]
80
80
  def get_class_variables(fqns)
81
81
  namespace_children(fqns).select{|pin| pin.is_a?(Pin::ClassVariable)}
82
82
  end
83
83
 
84
- # @return [Array<Solargraph::Pin::Base>]
84
+ # @return [Enumerable<Solargraph::Pin::Base>]
85
85
  def get_symbols
86
86
  symbols.uniq(&:name)
87
87
  end
@@ -97,12 +97,12 @@ module Solargraph
97
97
  @namespaces ||= Set.new
98
98
  end
99
99
 
100
- # @return [Array<Solargraph::Pin::Base>]
100
+ # @return [Enumerable<Solargraph::Pin::Base>]
101
101
  def namespace_pins
102
102
  pins_by_class(Solargraph::Pin::Namespace)
103
103
  end
104
104
 
105
- # @return [Array<Solargraph::Pin::Method>]
105
+ # @return [Enumerable<Solargraph::Pin::Method>]
106
106
  def method_pins
107
107
  pins_by_class(Solargraph::Pin::Method)
108
108
  end
@@ -131,7 +131,7 @@ module Solargraph
131
131
  end
132
132
  end
133
133
 
134
- # @return [Array<Pin::Block>]
134
+ # @return [Enumerable<Pin::Block>]
135
135
  def block_pins
136
136
  pins_by_class(Pin::Block)
137
137
  end
@@ -142,9 +142,9 @@ module Solargraph
142
142
  end
143
143
 
144
144
  # @param klass [Class]
145
- # @return [Array<Solargraph::Pin::Base>]
145
+ # @return [Enumerable<Solargraph::Pin::Base>]
146
146
  def pins_by_class klass
147
- @pin_select_cache[klass] ||= @pin_class_hash.select { |key, _| key <= klass }.values.flatten
147
+ @pin_select_cache[klass] ||= @pin_class_hash.each_with_object(Set.new) { |(key, o), n| n.merge(o) if key <= klass }
148
148
  end
149
149
 
150
150
  private
@@ -171,7 +171,7 @@ module Solargraph
171
171
  end
172
172
  end
173
173
 
174
- # @return [Array<Solargraph::Pin::Symbol>]
174
+ # @return [Enumerable<Solargraph::Pin::Symbol>]
175
175
  def symbols
176
176
  pins_by_class(Pin::Symbol)
177
177
  end
@@ -193,7 +193,7 @@ module Solargraph
193
193
  end
194
194
 
195
195
  # @param name [String]
196
- # @return [Array<Solargraph::Pin::Base>]
196
+ # @return [Enumerable<Solargraph::Pin::Base>]
197
197
  def namespace_children name
198
198
  namespace_map[name] || []
199
199
  end
@@ -216,8 +216,8 @@ module Solargraph
216
216
  set = pins.to_set
217
217
  @pin_class_hash = set.classify(&:class).transform_values(&:to_a)
218
218
  @pin_select_cache = {}
219
- @namespace_map = set.classify(&:namespace).transform_values(&:to_a)
220
- @path_pin_hash = set.classify(&:path).transform_values(&:to_a)
219
+ @namespace_map = set.classify(&:namespace)
220
+ @path_pin_hash = set.classify(&:path)
221
221
  @namespaces = @path_pin_hash.keys.compact.to_set
222
222
  pins_by_class(Pin::Reference::Include).each do |pin|
223
223
  include_references[pin.namespace] ||= []
@@ -138,7 +138,7 @@ module Solargraph
138
138
 
139
139
  # An array of pins based on Ruby keywords (`if`, `end`, etc.).
140
140
  #
141
- # @return [Array<Solargraph::Pin::Keyword>]
141
+ # @return [Enumerable<Solargraph::Pin::Keyword>]
142
142
  def keyword_pins
143
143
  store.pins_by_class(Pin::Keyword)
144
144
  end
@@ -72,9 +72,12 @@ module Solargraph
72
72
 
73
73
  # @return [String]
74
74
  def namespace
75
- @namespace ||= 'Object' if duck_type?
76
- @namespace ||= 'NilClass' if nil_type?
77
- @namespace ||= (name == 'Class' || name == 'Module') && !subtypes.empty? ? subtypes.first.name : name
75
+ # if priority higher than ||=, old implements cause unnecessary check
76
+ @namespace ||= lambda do
77
+ return 'Object' if duck_type?
78
+ return 'NilClass' if nil_type?
79
+ return (name == 'Class' || name == 'Module') && !subtypes.empty? ? subtypes.first.name : name
80
+ end.call
78
81
  end
79
82
 
80
83
  # @return [Symbol] :class or :instance
@@ -51,6 +51,10 @@ module Solargraph
51
51
  def select &block
52
52
  @items.select &block
53
53
  end
54
+ def namespace
55
+ # cache this attr for high frequency call
56
+ @namespace ||= method_missing(:namespace).to_s
57
+ end
54
58
 
55
59
  def method_missing name, *args, &block
56
60
  return if @items.first.nil?
@@ -59,18 +59,16 @@ module Solargraph
59
59
  # @return [Hash]
60
60
  def self.specs_from_bundle directory
61
61
  Solargraph.with_clean_env do
62
- Dir.chdir directory do
63
- cmd = [
64
- 'bundle', 'exec', 'ruby', '-e',
65
- "require 'bundler'; require 'json'; puts Bundler.definition.specs_for([:default]).map { |spec| [spec.name, spec.version] }.to_h.to_json"
66
- ]
67
- o, e, s = Open3.capture3(*cmd)
68
- if s.success?
69
- o && !o.empty? ? JSON.parse(o.split("\n").last) : {}
70
- else
71
- Solargraph.logger.warn e
72
- raise BundleNotFoundError, "Failed to load gems from bundle at #{directory}"
73
- end
62
+ cmd = [
63
+ 'ruby', '-e',
64
+ "require 'bundler'; require 'json'; Dir.chdir('#{directory}') { puts Bundler.definition.specs_for([:default]).map { |spec| [spec.name, spec.version] }.to_h.to_json }"
65
+ ]
66
+ o, e, s = Open3.capture3(*cmd)
67
+ if s.success?
68
+ o && !o.empty? ? JSON.parse(o.split("\n").last) : {}
69
+ else
70
+ Solargraph.logger.warn e
71
+ raise BundleNotFoundError, "Failed to load gems from bundle at #{directory}"
74
72
  end
75
73
  end
76
74
  end
@@ -41,7 +41,8 @@ module Solargraph
41
41
  result = explicit_library_for(uri) ||
42
42
  implicit_library_for(uri) ||
43
43
  generic_library_for(uri)
44
- result.attach sources.find(uri) if sources.include?(uri)
44
+ # previous library for already call attach. avoid call twice
45
+ # result.attach sources.find(uri) if sources.include?(uri)
45
46
  result
46
47
  end
47
48
 
@@ -82,7 +83,6 @@ module Solargraph
82
83
  libraries.each do |lib|
83
84
  if filename.start_with?(lib.workspace.directory)
84
85
  lib.attach sources.find(uri)
85
- lib.catalog
86
86
  return lib
87
87
  end
88
88
  end
@@ -98,7 +98,6 @@ module Solargraph
98
98
  # @return [Library]
99
99
  def generic_library_for uri
100
100
  generic_library.attach sources.find(uri)
101
- generic_library.catalog
102
101
  generic_library
103
102
  end
104
103
 
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module LanguageServer
5
+ class Host
6
+ # A serial worker Thread to handle message.
7
+ #
8
+ # this make check pending message possible, and maybe cancelled to speedup process
9
+ class MessageWorker
10
+ # @param host [Host]
11
+ def initialize(host)
12
+ @host = host
13
+ @mutex = Mutex.new
14
+ @resource = ConditionVariable.new
15
+ @stopped = true
16
+ end
17
+
18
+ # pending handle messages
19
+ def messages
20
+ @messages ||= []
21
+ end
22
+
23
+ def stopped?
24
+ @stopped
25
+ end
26
+
27
+ def stop
28
+ @stopped = true
29
+ end
30
+
31
+ # @param message [Hash] The message should be handle. will pass back to Host#receive
32
+ # @return [void]
33
+ def queue(message)
34
+ @mutex.synchronize do
35
+ messages.push(message)
36
+ @resource.signal
37
+ end
38
+ end
39
+
40
+ def start
41
+ return unless @stopped
42
+ @stopped = false
43
+ Thread.new do
44
+ tick until stopped?
45
+ end
46
+ end
47
+
48
+ def tick
49
+ message = @mutex.synchronize do
50
+ @resource.wait(@mutex) if messages.empty?
51
+ messages.shift
52
+ end
53
+ handler = @host.receive(message)
54
+ handler && handler.send_response
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -12,10 +12,12 @@ module Solargraph
12
12
  # safety for multi-threaded transports.
13
13
  #
14
14
  class Host
15
- autoload :Diagnoser, 'solargraph/language_server/host/diagnoser'
16
- autoload :Cataloger, 'solargraph/language_server/host/cataloger'
17
- autoload :Sources, 'solargraph/language_server/host/sources'
18
- autoload :Dispatch, 'solargraph/language_server/host/dispatch'
15
+ autoload :Diagnoser, 'solargraph/language_server/host/diagnoser'
16
+ autoload :Cataloger, 'solargraph/language_server/host/cataloger'
17
+ autoload :Sources, 'solargraph/language_server/host/sources'
18
+ autoload :Dispatch, 'solargraph/language_server/host/dispatch'
19
+ autoload :MessageWorker, 'solargraph/language_server/host/message_worker'
20
+
19
21
 
20
22
  include UriHelpers
21
23
  include Logging
@@ -27,7 +29,7 @@ module Solargraph
27
29
  def initialize
28
30
  @cancel_semaphore = Mutex.new
29
31
  @buffer_semaphore = Mutex.new
30
- @register_semaphore = Mutex.new
32
+ @request_mutex = Mutex.new
31
33
  @cancel = []
32
34
  @buffer = String.new
33
35
  @stopped = true
@@ -45,6 +47,7 @@ module Solargraph
45
47
  diagnoser.start
46
48
  cataloger.start
47
49
  sources.start
50
+ message_worker.start
48
51
  end
49
52
 
50
53
  # Update the configuration options with the provided hash.
@@ -89,8 +92,15 @@ module Solargraph
89
92
  @cancel_semaphore.synchronize { @cancel.delete id }
90
93
  end
91
94
 
95
+ # Called by adapter, to handle the request
96
+ # @param request [Hash]
97
+ # @return [void]
98
+ def process request
99
+ message_worker.queue(request)
100
+ end
101
+
92
102
  # Start processing a request from the client. After the message is
93
- # processed, the transport is responsible for sending the response.
103
+ # processed, caller is responsible for sending the response.
94
104
  #
95
105
  # @param request [Hash] The contents of the message.
96
106
  # @return [Solargraph::LanguageServer::Message::Base] The message handler.
@@ -355,19 +365,21 @@ module Solargraph
355
365
  # @yieldparam [Hash] The result sent by the client
356
366
  # @return [void]
357
367
  def send_request method, params, &block
358
- message = {
359
- jsonrpc: "2.0",
360
- method: method,
361
- params: params,
362
- id: @next_request_id
363
- }
364
- json = message.to_json
365
- requests[@next_request_id] = Request.new(@next_request_id, &block)
366
- envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
367
- queue envelope
368
- @next_request_id += 1
369
- logger.info "Server sent #{method}"
370
- logger.debug params
368
+ @request_mutex.synchronize do
369
+ message = {
370
+ jsonrpc: "2.0",
371
+ method: method,
372
+ params: params,
373
+ id: @next_request_id
374
+ }
375
+ json = message.to_json
376
+ requests[@next_request_id] = Request.new(@next_request_id, &block)
377
+ envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
378
+ queue envelope
379
+ @next_request_id += 1
380
+ logger.info "Server sent #{method}"
381
+ logger.debug params
382
+ end
371
383
  end
372
384
 
373
385
  # Register the methods as capabilities with the client.
@@ -378,20 +390,16 @@ module Solargraph
378
390
  # @return [void]
379
391
  def register_capabilities methods
380
392
  logger.debug "Registering capabilities: #{methods}"
381
- registrations = methods.select{|m| can_register?(m) and !registered?(m)}.map { |m|
393
+ registrations = methods.select { |m| can_register?(m) and !registered?(m) }.map do |m|
382
394
  @registered_capabilities.add m
383
395
  {
384
396
  id: m,
385
397
  method: m,
386
398
  registerOptions: dynamic_capability_options[m]
387
399
  }
388
- }
389
- return if registrations.empty?
390
- @register_semaphore.synchronize do
391
- send_request 'client/registerCapability', {
392
- registrations: registrations
393
- }
394
400
  end
401
+ return if registrations.empty?
402
+ send_request 'client/registerCapability', { registrations: registrations }
395
403
  end
396
404
 
397
405
  # Unregister the methods with the client.
@@ -410,11 +418,7 @@ module Solargraph
410
418
  }
411
419
  }
412
420
  return if unregisterations.empty?
413
- @register_semaphore.synchronize do
414
- send_request 'client/unregisterCapability', {
415
- unregisterations: unregisterations
416
- }
417
- end
421
+ send_request 'client/unregisterCapability', { unregisterations: unregisterations }
418
422
  end
419
423
 
420
424
  # Flag a method as available for dynamic registration.
@@ -422,9 +426,7 @@ module Solargraph
422
426
  # @param method [String] The method name, e.g., 'textDocument/completion'
423
427
  # @return [void]
424
428
  def allow_registration method
425
- @register_semaphore.synchronize do
426
- @dynamic_capabilities.add method
427
- end
429
+ @dynamic_capabilities.add method
428
430
  end
429
431
 
430
432
  # True if the specified LSP method can be dynamically registered.
@@ -451,6 +453,7 @@ module Solargraph
451
453
  def stop
452
454
  return if @stopped
453
455
  @stopped = true
456
+ message_worker.stop
454
457
  cataloger.stop
455
458
  diagnoser.stop
456
459
  sources.stop
@@ -513,6 +516,11 @@ module Solargraph
513
516
  library.completions_at uri_to_file(uri), line, column
514
517
  end
515
518
 
519
+ # @return [Bool] if has pending completion request
520
+ def has_pending_completions?
521
+ message_worker.messages.reverse_each.any? { |req| req['method'] == 'textDocument/completion' }
522
+ end
523
+
516
524
  # @param uri [String]
517
525
  # @param line [Integer]
518
526
  # @param column [Integer]
@@ -646,6 +654,11 @@ module Solargraph
646
654
 
647
655
  private
648
656
 
657
+ # @return [MessageWorker]
658
+ def message_worker
659
+ @message_worker ||= MessageWorker.new(self)
660
+ end
661
+
649
662
  # @return [Diagnoser]
650
663
  def diagnoser
651
664
  @diagnoser ||= Diagnoser.new(self)
@@ -62,10 +62,14 @@ module Solargraph
62
62
  def send_response
63
63
  return if id.nil?
64
64
  if host.cancel?(id)
65
+ # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#cancelRequest
66
+ # cancel should send response RequestCancelled
65
67
  Solargraph::Logging.logger.info "Cancelled response to #{method}"
66
- return
68
+ set_result nil
69
+ set_error ErrorCodes::REQUEST_CANCELLED, "cancelled by client"
70
+ else
71
+ Solargraph::Logging.logger.info "Sending response to #{method}"
67
72
  end
68
- Solargraph::Logging.logger.info "Sending response to #{method}"
69
73
  response = {
70
74
  jsonrpc: "2.0",
71
75
  id: id,
@@ -6,6 +6,8 @@ module Solargraph
6
6
  module TextDocument
7
7
  class Completion < Base
8
8
  def process
9
+ return set_error(ErrorCodes::REQUEST_CANCELLED, "cancelled by so many request") if host.has_pending_completions?
10
+
9
11
  line = params['position']['line']
10
12
  col = params['position']['character']
11
13
  begin
@@ -41,8 +41,7 @@ module Solargraph
41
41
  # @param request [String]
42
42
  # @return [void]
43
43
  def process request
44
- message = @host.receive(request)
45
- message && message.send_response
44
+ @host.process(request)
46
45
  end
47
46
 
48
47
  def shutdown
@@ -56,7 +56,7 @@ module Solargraph
56
56
  end
57
57
  @current = source
58
58
  maybe_map @current
59
- api_map.catalog bench unless synchronized?
59
+ catalog_inlock
60
60
  end
61
61
  end
62
62
 
@@ -368,12 +368,16 @@ module Solargraph
368
368
  # @return [void]
369
369
  def catalog
370
370
  mutex.synchronize do
371
- break if synchronized?
371
+ catalog_inlock
372
+ end
373
+ end
374
+
375
+ private def catalog_inlock
376
+ return if synchronized?
372
377
  logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}"
373
378
  api_map.catalog bench
374
379
  @synchronized = true
375
380
  logger.info "Catalog complete (#{api_map.source_maps.length} files, #{api_map.pins.length} pins)" if logger.info?
376
- end
377
381
  end
378
382
 
379
383
  def bench
@@ -514,6 +518,7 @@ module Solargraph
514
518
 
515
519
  def maybe_map source
516
520
  return unless source
521
+ return unless @current == source || workspace.has_file?(source.filename)
517
522
  if source_map_hash.key?(source.filename)
518
523
  return if source_map_hash[source.filename].code == source.code &&
519
524
  source_map_hash[source.filename].source.synchronized? &&
@@ -2,7 +2,7 @@ require 'ripper'
2
2
 
3
3
  module Solargraph
4
4
  module Parser
5
- class CommentRipper < Ripper::SexpBuilder
5
+ class CommentRipper < Ripper::SexpBuilderPP
6
6
  def initialize src, filename = '(ripper)', lineno = 0
7
7
  super
8
8
  @buffer = src
@@ -103,6 +103,31 @@ module Solargraph
103
103
  en = Position.new(node.loc.last_line, node.loc.last_column)
104
104
  Range.new(st, en)
105
105
  end
106
+
107
+ def string_ranges node
108
+ return [] unless is_ast_node?(node)
109
+ result = []
110
+ if node.type == :str
111
+ result.push Range.from_node(node)
112
+ elsif node.type == :dstr
113
+ here = Range.from_node(node)
114
+ there = Range.from_node(node.children[1])
115
+ result.push Range.new(here.start, there.start)
116
+ end
117
+ node.children.each do |child|
118
+ result.concat string_ranges(child)
119
+ end
120
+ if node.type == :dstr && node.children.last.nil?
121
+ # result.push Range.new(result.last.ending, result.last.ending)
122
+ last = node.children[-2]
123
+ unless last.nil?
124
+ rng = Range.from_node(last)
125
+ pos = Position.new(rng.ending.line, rng.ending.column - 1)
126
+ result.push Range.new(pos, pos)
127
+ end
128
+ end
129
+ result
130
+ end
106
131
  end
107
132
  end
108
133
  end
@@ -102,13 +102,26 @@ module Solargraph
102
102
  result.concat generate_links(n.children[0])
103
103
  elsif n.type == :block_pass
104
104
  result.push Chain::BlockVariable.new("&#{n.children[0].children[0].to_s}")
105
+ elsif n.type == :hash
106
+ result.push Chain::Hash.new('::Hash', hash_is_splatted?(n))
105
107
  else
106
108
  lit = infer_literal_node_type(n)
107
- result.push (lit ? Chain::Literal.new(lit) : Chain::Link.new)
109
+ # if lit == '::Hash'
110
+ # result.push Chain::Hash.new(lit, hash_is_splatted?(n))
111
+ # else
112
+ result.push (lit ? Chain::Literal.new(lit) : Chain::Link.new)
113
+ # end
108
114
  end
109
115
  result
110
116
  end
111
117
 
118
+ def hash_is_splatted? node
119
+ return false unless Parser.is_ast_node?(node) && node.type == :hash
120
+ return false unless Parser.is_ast_node?(node.children.last) && node.children.last.type == :kwsplat
121
+ return false if Parser.is_ast_node?(node.children.last.children[0]) && node.children.last.children[0].type == :hash
122
+ true
123
+ end
124
+
112
125
  def block_passed? node
113
126
  node.children.last.is_a?(::Parser::AST::Node) && node.children.last.type == :block_pass
114
127
  end
@@ -97,8 +97,10 @@ module Solargraph
97
97
  end
98
98
 
99
99
  def convert_hash node
100
- return {} unless Parser.is_ast_node?(node) && node.type == :hash
101
- return convert_hash(node.children[0].children[0]) if splatted_hash?(node)
100
+ return {} unless Parser.is_ast_node?(node)
101
+ return convert_hash(node.children[0]) if node.type == :kwsplat
102
+ return convert_hash(node.children[0]) if Parser.is_ast_node?(node.children[0]) && node.children[0].type == :kwsplat
103
+ return {} unless node.type == :hash
102
104
  result = {}
103
105
  node.children.each do |pair|
104
106
  result[pair.children[0].children[0]] = Solargraph::Parser.chain(pair.children[1])
@@ -124,9 +126,14 @@ module Solargraph
124
126
  end
125
127
 
126
128
  def splatted_call? node
129
+ return false unless Parser.is_ast_node?(node)
127
130
  Parser.is_ast_node?(node.children[0]) && node.children[0].type == :kwsplat && node.children[0].children[0].type != :hash
128
131
  end
129
132
 
133
+ def any_splatted_call?(nodes)
134
+ nodes.any? { |n| splatted_call?(n) }
135
+ end
136
+
130
137
  # @todo Temporarily here for testing. Move to Solargraph::Parser.
131
138
  def call_nodes_from node
132
139
  return [] unless node.is_a?(::Parser::AST::Node)
@@ -97,13 +97,31 @@ module Solargraph
97
97
  result.concat generate_links(n.children[0])
98
98
  elsif n.type == :BLOCK_PASS
99
99
  result.push Chain::BlockVariable.new("&#{n.children[1].children[0].to_s}")
100
+ elsif n.type == :HASH
101
+ result.push Chain::Hash.new('::Hash', hash_is_splatted?(n))
100
102
  else
101
103
  lit = infer_literal_node_type(n)
102
- result.push (lit ? Chain::Literal.new(lit) : Chain::Link.new)
104
+ if lit
105
+ if lit == '::Hash'
106
+ result.push Chain::Hash.new(lit, hash_is_splatted?(n))
107
+ else
108
+ result.push Chain::Literal.new(lit)
109
+ end
110
+ else
111
+ result.push Chain::Link.new
112
+ end
113
+ # result.push (lit ? Chain::Literal.new(lit) : Chain::Link.new)
103
114
  end
104
115
  result
105
116
  end
106
117
 
118
+ def hash_is_splatted? node
119
+ return false unless Parser.is_ast_node?(node.children[0]) && node.children[0].type == :LIST
120
+ list = node.children[0].children
121
+ eol = list.rindex(&:nil?)
122
+ eol && Parser.is_ast_node?(list[eol + 1])
123
+ end
124
+
107
125
  def block_passed? node
108
126
  node.children.last.is_a?(RubyVM::AbstractSyntaxTree::Node) && node.children.last.type == :BLOCK_PASS
109
127
  end
@@ -114,6 +132,9 @@ module Solargraph
114
132
  node.children[0..-2].map { |c| NodeChainer.chain(c) }
115
133
  elsif node.type == :SPLAT
116
134
  [NodeChainer.chain(node)]
135
+ elsif node.type == :ARGSPUSH
136
+ result = node_to_argchains(node.children[0])
137
+ result.push NodeChainer.chain(node.children[1]) if Parser.is_ast_node?(node.children[1])
117
138
  elsif node.type == :ARGSCAT
118
139
  result = node.children[0].children[0..-2].map { |c| NodeChainer.chain(c) }
119
140
  result.push NodeChainer.chain(node.children[1])
@@ -117,9 +117,14 @@ module Solargraph
117
117
  end
118
118
 
119
119
  def splatted_call? node
120
+ return false unless Parser.is_ast_node?(node)
120
121
  splatted_node?(node) && node.children[0].children[1].type != :HASH
121
122
  end
122
123
 
124
+ def any_splatted_call?(nodes)
125
+ nodes.any? { |n| splatted_call?(n) }
126
+ end
127
+
123
128
  def node? node
124
129
  node.is_a?(RubyVM::AbstractSyntaxTree::Node)
125
130
  end
@@ -218,7 +218,7 @@ module Solargraph
218
218
  end
219
219
 
220
220
  def inspect
221
- "#<#{self.class} at #{self.location.inspect}>"
221
+ "#<#{self.class} `#{self.path}` at #{self.location.inspect}>"
222
222
  end
223
223
 
224
224
  protected
@@ -22,12 +22,18 @@ module Solargraph
22
22
  @closure = Solargraph::Pin::ROOT_PIN
23
23
  end
24
24
  @open_gates = gates
25
- if @open_gates.one? && @open_gates.first.empty? && @name.include?('::')
25
+ if @name.include?('::')
26
26
  # In this case, a chained namespace was opened (e.g., Foo::Bar)
27
27
  # but Foo does not exist.
28
28
  parts = @name.split('::')
29
29
  @name = parts.pop
30
- @closure = Pin::Namespace.new(name: parts.join('::'), gates: [parts.join('::')])
30
+ closure_name = if [Solargraph::Pin::ROOT_PIN, nil].include?(closure)
31
+ ''
32
+ else
33
+ closure.full_context.namespace + '::'
34
+ end
35
+ closure_name += parts.join('::')
36
+ @closure = Pin::Namespace.new(name: closure_name, gates: [parts.join('::')])
31
37
  @context = nil
32
38
  end
33
39
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ class Source
5
+ class Chain
6
+ class Hash < Literal
7
+ # @param type [String]
8
+ # @param splatted [Boolean]
9
+ def initialize type, splatted = false
10
+ super(type)
11
+ @splatted = splatted
12
+ end
13
+
14
+ def word
15
+ @word ||= "<#{@type}>"
16
+ end
17
+
18
+ def resolve api_map, name_pin, locals
19
+ [Pin::ProxyType.anonymous(@complex_type)]
20
+ end
21
+
22
+ def splatted?
23
+ @splatted
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -21,6 +21,7 @@ module Solargraph
21
21
  autoload :Or, 'solargraph/source/chain/or'
22
22
  autoload :BlockVariable, 'solargraph/source/chain/block_variable'
23
23
  autoload :ZSuper, 'solargraph/source/chain/z_super'
24
+ autoload :Hash, 'solargraph/source/chain/hash'
24
25
 
25
26
  @@inference_stack = []
26
27
  @@inference_depth = 0
@@ -213,7 +213,7 @@ module Solargraph
213
213
  result.concat api_map.get_methods(block.binder.namespace, scope: block.binder.scope, visibility: [:public, :private, :protected])
214
214
  result.concat api_map.get_methods('Kernel')
215
215
  # result.concat ApiMap.keywords
216
- result.concat api_map.keyword_pins
216
+ result.concat api_map.keyword_pins.to_a
217
217
  result.concat yielded_self_pins
218
218
  end
219
219
  end
@@ -204,6 +204,8 @@ module Solargraph
204
204
  com_pos = Position.new(line + 1 - comments.lines.length, 0)
205
205
  process_comment(src_pos, com_pos, comments)
206
206
  end
207
+ rescue StandardError => e
208
+ raise e.class, "Error processing comment directives in #{@filename}: #{e.message}"
207
209
  end
208
210
  end
209
211
  end
@@ -148,6 +148,7 @@ module Solargraph
148
148
  all_variables.each do |pin|
149
149
  if pin.return_type.defined?
150
150
  declared = pin.typify(api_map)
151
+ next if declared.duck_type?
151
152
  if declared.defined?
152
153
  if rules.validate_tags?
153
154
  inferred = pin.probe(api_map)
@@ -416,7 +417,7 @@ module Solargraph
416
417
  end
417
418
  settled_kwargs = 0
418
419
  unless unchecked.empty?
419
- if Parser.is_ast_node?(unchecked.last.node) && splatted_call?(unchecked.last.node)
420
+ if any_splatted_call?(unchecked.map(&:node))
420
421
  settled_kwargs = pin.parameters.count(&:keyword?)
421
422
  else
422
423
  kwargs = convert_hash(unchecked.last.node)
@@ -431,6 +432,7 @@ module Solargraph
431
432
  kwargs.delete param.name.to_sym
432
433
  settled_kwargs += 1
433
434
  elsif param.decl == :kwarg
435
+ return [] if arguments.last.links.last.is_a?(Solargraph::Source::Chain::Hash) && arguments.last.links.last.splatted?
434
436
  return [Problem.new(location, "Missing keyword argument #{param.name} to #{pin.path}")]
435
437
  end
436
438
  end
@@ -450,12 +452,17 @@ module Solargraph
450
452
  if unchecked.length == req + opt + 1 && unchecked.last.links.last.is_a?(Source::Chain::BlockVariable)
451
453
  return []
452
454
  end
453
- if req + add_params + 1 == unchecked.length && splatted_call?(unchecked.last.node) && (pin.parameters.map(&:decl) & [:kwarg, :kwoptarg, :kwrestarg]).any?
455
+ if req + add_params + 1 == unchecked.length && any_splatted_call?(unchecked.map(&:node)) && (pin.parameters.map(&:decl) & [:kwarg, :kwoptarg, :kwrestarg]).any?
454
456
  return []
455
457
  end
458
+ return [] if arguments.length - req == pin.parameters.select { |p| [:optarg, :kwoptarg].include?(p.decl) }.length
456
459
  return [Problem.new(location, "Too many arguments to #{pin.path}")]
457
- elsif unchecked.length < req - settled_kwargs && (arguments.empty? || !arguments.last.splat?)
458
- return [Problem.new(location, "Not enough arguments to #{pin.path}")]
460
+ elsif unchecked.length < req - settled_kwargs && (arguments.empty? || (!arguments.last.splat? && !arguments.last.links.last.is_a?(Solargraph::Source::Chain::Hash)))
461
+ # HACK: Kernel#raise signature is incorrect in Ruby 2.7 core docs.
462
+ # See https://github.com/castwide/solargraph/issues/418
463
+ unless arguments.empty? && pin.path == 'Kernel#raise'
464
+ return [Problem.new(location, "Not enough arguments to #{pin.path}")]
465
+ end
459
466
  end
460
467
  []
461
468
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Solargraph
4
- VERSION = '0.42.4'
4
+ VERSION = '0.43.3'
5
5
  end
@@ -2,7 +2,7 @@
2
2
  Namespace:
3
3
  </h2>
4
4
  <p>
5
- <a href="command:solargraph._openDocument?<%= CGI.escape "\"solargraph:/document?query=#{object.namespace}\"" %>"><%= object.namespace %></a>
5
+ <a href="solargraph:/document?query=<%= CGI.escape object.namespace.path %>"><%= object.namespace %></a>
6
6
  </p>
7
7
  <h2>
8
8
  Overview:
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'open3'
4
+ require 'rubygems'
5
+
3
6
  module Solargraph
4
7
  # A workspace consists of the files in a project's directory and the
5
8
  # project's configuration. It provides a Source for each file to be used
@@ -168,19 +171,20 @@ module Solargraph
168
171
  # HACK: Evaluating gemspec files violates the goal of not running
169
172
  # workspace code, but this is how Gem::Specification.load does it
170
173
  # anyway.
171
- Dir.chdir base do
174
+ cmd = ['ruby', '-e', "require 'rubygems'; require 'json'; spec = eval(File.read('#{file}'), TOPLEVEL_BINDING, '#{file}'); return unless Gem::Specification === spec; puts({name: spec.name, paths: spec.require_paths}.to_json)"]
175
+ o, e, s = Open3.capture3(*cmd)
176
+ if s.success?
172
177
  begin
173
- # @type [Gem::Specification]
174
- spec = eval(File.read(file), TOPLEVEL_BINDING, file)
175
- next unless Gem::Specification === spec
176
- @gemnames.push spec.name
177
- result.concat(spec.require_paths.map { |path| File.join(base, path) })
178
- rescue RuntimeError, ScriptError, Errno::ENOENT => e
179
- # Don't die if we have an error during eval-ing a gem spec.
180
- # Concat the default lib directory instead.
178
+ hash = o && !o.empty? ? JSON.parse(o.split("\n").last) : {}
179
+ next if hash.empty?
180
+ @gemnames.push hash['name']
181
+ result.concat(hash['paths'].map { |path| File.join(base, path) })
182
+ rescue StandardError => e
181
183
  Solargraph.logger.warn "Error reading #{file}: [#{e.class}] #{e.message}"
182
- result.push File.join(base, 'lib')
183
184
  end
185
+ else
186
+ Solargraph.logger.warn "Error reading #{file}"
187
+ Solargraph.logger.warn e
184
188
  end
185
189
  end
186
190
  result.concat(config.require_paths.map { |p| File.join(directory, p) })
@@ -10,6 +10,8 @@ module Solargraph
10
10
  # stdlib, and gems.
11
11
  #
12
12
  class YardMap
13
+ class NoYardocError < StandardError; end
14
+
13
15
  autoload :Cache, 'solargraph/yard_map/cache'
14
16
  autoload :CoreDocs, 'solargraph/yard_map/core_docs'
15
17
  autoload :CoreGen, 'solargraph/yard_map/core_gen'
@@ -224,7 +226,7 @@ module Solargraph
224
226
  result.concat process_yardoc yd, spec
225
227
  result.concat add_gem_dependencies(spec) if with_dependencies?
226
228
  end
227
- rescue Gem::LoadError => e
229
+ rescue Gem::LoadError, NoYardocError => e
228
230
  base = r.split('/').first
229
231
  next if from_std.include?(base)
230
232
  from_std.push base
@@ -299,6 +301,7 @@ module Solargraph
299
301
  result = Marshal.load(dump)
300
302
  return result unless result.nil? || result.empty?
301
303
  Solargraph.logger.warn "Empty cache for #{spec.name} #{spec.version}. Reloading"
304
+ File.unlink ser
302
305
  rescue StandardError => e
303
306
  Solargraph.logger.warn "Error loading pin cache: [#{e.class}] #{e.message}"
304
307
  File.unlink ser
@@ -315,6 +318,7 @@ module Solargraph
315
318
  Solargraph.logger.info "Loading #{spec.name} #{spec.version} from #{y}"
316
319
  load_yardoc y
317
320
  result = Mapper.new(YARD::Registry.all, spec).map
321
+ raise NoYardocError, "Yardoc at #{y} is empty" if result.empty?
318
322
  if spec
319
323
  ser = File.join(CoreDocs.cache_dir, 'gems', "#{spec.name}-#{spec.version}.ser")
320
324
  file = File.open(ser, 'wb')
data/lib/solargraph.rb CHANGED
@@ -57,6 +57,7 @@ module Solargraph
57
57
  # A helper method that runs Bundler.with_unbundled_env or falls back to
58
58
  # Bundler.with_clean_env for earlier versions of Bundler.
59
59
  #
60
+ # @return [void]
60
61
  def self.with_clean_env &block
61
62
  meth = if Bundler.respond_to?(:with_unbundled_env)
62
63
  :with_unbundled_env
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solargraph
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.42.4
4
+ version: 0.43.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fred Snyder
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-11 00:00:00.000000000 Z
11
+ date: 2021-09-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: backport
@@ -350,6 +350,7 @@ files:
350
350
  - lib/solargraph/language_server/host/cataloger.rb
351
351
  - lib/solargraph/language_server/host/diagnoser.rb
352
352
  - lib/solargraph/language_server/host/dispatch.rb
353
+ - lib/solargraph/language_server/host/message_worker.rb
353
354
  - lib/solargraph/language_server/host/sources.rb
354
355
  - lib/solargraph/language_server/message.rb
355
356
  - lib/solargraph/language_server/message/base.rb
@@ -502,6 +503,7 @@ files:
502
503
  - lib/solargraph/source/chain/class_variable.rb
503
504
  - lib/solargraph/source/chain/constant.rb
504
505
  - lib/solargraph/source/chain/global_variable.rb
506
+ - lib/solargraph/source/chain/hash.rb
505
507
  - lib/solargraph/source/chain/head.rb
506
508
  - lib/solargraph/source/chain/instance_variable.rb
507
509
  - lib/solargraph/source/chain/link.rb