solargraph 0.42.3 → 0.43.2

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/README.md +2 -1
  4. data/SPONSORS.md +0 -1
  5. data/lib/solargraph/api_map/store.rb +17 -17
  6. data/lib/solargraph/api_map.rb +1 -1
  7. data/lib/solargraph/complex_type/type_methods.rb +6 -3
  8. data/lib/solargraph/complex_type.rb +4 -0
  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 -4
  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/node_processor/base.rb +0 -3
  21. data/lib/solargraph/parser/rubyvm/node_chainer.rb +22 -1
  22. data/lib/solargraph/parser/rubyvm/node_methods.rb +5 -0
  23. data/lib/solargraph/pin/base.rb +1 -1
  24. data/lib/solargraph/pin/namespace.rb +8 -2
  25. data/lib/solargraph/source/chain/hash.rb +28 -0
  26. data/lib/solargraph/source/chain.rb +1 -4
  27. data/lib/solargraph/source.rb +0 -12
  28. data/lib/solargraph/source_map/clip.rb +1 -1
  29. data/lib/solargraph/source_map/mapper.rb +2 -0
  30. data/lib/solargraph/type_checker.rb +11 -4
  31. data/lib/solargraph/version.rb +1 -1
  32. data/lib/solargraph/views/_method.erb +1 -1
  33. data/lib/solargraph/workspace.rb +1 -1
  34. data/lib/solargraph/yard_map.rb +7 -7
  35. data/lib/solargraph.rb +1 -0
  36. data/lib/yard-solargraph.rb +3 -0
  37. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f99f80989cced14064f1739f7a193f207fe92c8f696374de2f4798e3662196f0
4
- data.tar.gz: efb7011bebf1eaab6b374c093fe25abe67a7b5da469024d78702feae1e4449a3
3
+ metadata.gz: e671eddad2fe717ad573954a63154bd89adbc12c9899c3f8762bf38a93160657
4
+ data.tar.gz: 2224d1457b9e8f7279bfa89b2777fe3d60a0a2283de4047930bebb79034a1ac4
5
5
  SHA512:
6
- metadata.gz: b3975f82cf5ac9c25e6d49dfe5cee21fc780d98f38dfca83371750c8a7913d53684d142f21047fcf132cdb7e02efb878b8956aab94b982ae764a3dcae9bf62cc
7
- data.tar.gz: 6917cfd70a9d436792456857154b6de5c10ded3d08f3ae9caf029307581577fcd1d1934fe3959c5c84750006d9e3d6a55c4a344746dee72eb8fa9dcf3a25e078
6
+ metadata.gz: 76ffe1331d8020eed92c39d2c4886ce7d01e4143a8b8264ac1bf4011335fabfd32a48f16a7099f889fed2e7c485e2e9cbf09714f677cd6e9396381032522f67a
7
+ data.tar.gz: 9c6da25c26ed209af38be4a0bc8d7a6ce826fb3012bce650089e982f829f4c8b14807ed0f7d1ecdeacd9b737bedd9d682440a9701a864313a83e60182771ade0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,26 @@
1
+ ## 0.43.2 - September 23, 2021
2
+ - Synchronize server requests (#461)
3
+
4
+ ## 0.43.1 - September 20, 2021
5
+ - Complete nested namespaces in open gates
6
+ - SourceMap::Mapper reports filename for encoding errors (#474)
7
+ - Handle request on a specific thread, and cancel completion when there has newer completion request (#459)
8
+ - Fix namespace links generated by views/_method.erb (#472)
9
+ - Source handles long squiggly heredocs (#460)
10
+
11
+ ## 0.43.0 - July 25, 2021
12
+ - Correct arity checks when restarg precedes arg (#418)
13
+ - Improve the performance of catalog by 4 times (#457)
14
+ - Type checker validates duck type variables and params (#453)
15
+ - Kernel#raise exception type checker
16
+ - Pin::Base#inspect includes path
17
+ - Fix arity with combined restargs and kwrestargs (#396)
18
+
19
+ ## 0.42.4 - July 11, 2021
20
+ - Yardoc cache handling
21
+ - Fix required_paths when gemspec is used (#451)
22
+ - fix: yard stdout may break language client (#454)
23
+
1
24
  ## 0.42.3 - June 14, 2021
2
25
  - Require 'pathname' for Library
3
26
 
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
data/SPONSORS.md CHANGED
@@ -13,4 +13,3 @@ The following people and organizations provide funding or other resources. [Beco
13
13
  - Emily Strickland
14
14
  - Tom de Grunt
15
15
  - Akira Yamada
16
- - Jared White
@@ -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?
@@ -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
 
@@ -351,7 +351,7 @@ module Solargraph
351
351
  else
352
352
  args = line.split(':').map(&:strip)
353
353
  name = args.shift
354
- reporter = Diagnostics.reporter(name)
354
+ reporter = Diagnostics.reporter(name)
355
355
  raise DiagnosticsError, "Diagnostics reporter #{name} does not exist" if reporter.nil?
356
356
  repargs[reporter] ||= []
357
357
  repargs[reporter].concat args
@@ -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
@@ -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)
@@ -4,9 +4,6 @@ module Solargraph
4
4
  module Parser
5
5
  module NodeProcessor
6
6
  class Base
7
- # @todo The base node processor should not include legacy node methods
8
- # include Legacy::NodeMethods
9
-
10
7
  # @return [Parser::AST::Node]
11
8
  attr_reader :node
12
9
 
@@ -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
@@ -61,10 +62,6 @@ module Solargraph
61
62
  working_pin = name_pin
62
63
  links[0..-2].each do |link|
63
64
  pins = link.resolve(api_map, working_pin, locals)
64
- # Locals are only used when resolving the first link
65
- # @todo There's a problem here. Call links need to resolve arguments
66
- # that might refer to local variables.
67
- # locals = []
68
65
  type = infer_first_defined(pins, working_pin, api_map)
69
66
  return [] if type.undefined?
70
67
  working_pin = Pin::ProxyType.anonymous(type)
@@ -6,19 +6,15 @@ module Solargraph
6
6
  # A Ruby file that has been parsed into an AST.
7
7
  #
8
8
  class Source
9
- # autoload :FlawedBuilder, 'solargraph/source/flawed_builder'
10
9
  autoload :Updater, 'solargraph/source/updater'
11
10
  autoload :Change, 'solargraph/source/change'
12
11
  autoload :Mapper, 'solargraph/source/mapper'
13
- # autoload :NodeMethods, 'solargraph/source/node_methods'
14
12
  autoload :EncodingFixes, 'solargraph/source/encoding_fixes'
15
13
  autoload :Cursor, 'solargraph/source/cursor'
16
14
  autoload :Chain, 'solargraph/source/chain'
17
15
  autoload :SourceChainer, 'solargraph/source/source_chainer'
18
- # autoload :NodeChainer, 'solargraph/source/node_chainer'
19
16
 
20
17
  include EncodingFixes
21
- # include NodeMethods
22
18
 
23
19
  # @return [String]
24
20
  attr_reader :filename
@@ -50,17 +46,9 @@ module Solargraph
50
46
  @node, @comments = Solargraph::Parser.parse_with_comments(@code, filename)
51
47
  @parsed = true
52
48
  rescue Parser::SyntaxError, EncodingError => e
53
- # @todo 100% whitespace results in a nil node, so there's no reason to parse it.
54
- # We still need to determine whether the resulting node should be nil or a dummy
55
- # node with a location that encompasses the range.
56
- # @node, @comments = Source.parse_with_comments(@code.gsub(/[^\s]/, ' '), filename)
57
49
  @node = nil
58
50
  @comments = {}
59
51
  @parsed = false
60
- # rescue Exception => e
61
- # Solargraph.logger.warn "[#{e.class}] #{e.message}"
62
- # Solargraph.logger.warn e.backtrace.join("\n")
63
- # raise "Error parsing #{filename || '(source)'}: [#{e.class}] #{e.message}"
64
52
  ensure
65
53
  @code.freeze
66
54
  end
@@ -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.3'
4
+ VERSION = '0.43.2'
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:
@@ -183,7 +183,7 @@ module Solargraph
183
183
  end
184
184
  end
185
185
  end
186
- result.concat config.require_paths
186
+ result.concat(config.require_paths.map { |p| File.join(directory, p) })
187
187
  result.push File.join(directory, 'lib') if result.empty?
188
188
  result
189
189
  end
@@ -288,9 +288,6 @@ module Solargraph
288
288
  # @return [Array<Pin::Base>]
289
289
  def process_yardoc y, spec = nil
290
290
  return [] if y.nil?
291
- size = Dir.glob(File.join(y, '**', '*'))
292
- .map{ |f| File.size(f) }
293
- .inject(:+)
294
291
  if spec
295
292
  ser = File.join(CoreDocs.cache_dir, 'gems', "#{spec.name}-#{spec.version}.ser")
296
293
  if File.file?(ser)
@@ -299,19 +296,24 @@ module Solargraph
299
296
  dump = file.read
300
297
  file.close
301
298
  begin
302
- return Marshal.load(dump)
299
+ result = Marshal.load(dump)
300
+ return result unless result.nil? || result.empty?
301
+ Solargraph.logger.warn "Empty cache for #{spec.name} #{spec.version}. Reloading"
303
302
  rescue StandardError => e
304
303
  Solargraph.logger.warn "Error loading pin cache: [#{e.class}] #{e.message}"
305
304
  File.unlink ser
306
305
  end
307
306
  end
308
307
  end
308
+ size = Dir.glob(File.join(y, '**', '*'))
309
+ .map{ |f| File.size(f) }
310
+ .inject(:+)
309
311
  if !size.nil? && size > 20_000_000
310
312
  Solargraph::Logging.logger.warn "Yardoc at #{y} is too large to process (#{size} bytes)"
311
313
  return []
312
314
  end
315
+ Solargraph.logger.info "Loading #{spec.name} #{spec.version} from #{y}"
313
316
  load_yardoc y
314
- Solargraph.logger.info "Loading #{spec.name} #{spec.version} from yardoc"
315
317
  result = Mapper.new(YARD::Registry.all, spec).map
316
318
  if spec
317
319
  ser = File.join(CoreDocs.cache_dir, 'gems', "#{spec.name}-#{spec.version}.ser")
@@ -435,5 +437,3 @@ module Solargraph
435
437
  end
436
438
 
437
439
  Solargraph::YardMap::CoreDocs.require_minimum
438
- # Change YARD log IO to avoid sending unexpected messages to STDOUT
439
- YARD::Logger.instance.io = File.new(File::NULL, 'w')
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
@@ -2,6 +2,9 @@
2
2
 
3
3
  require 'yard'
4
4
 
5
+ # Change YARD log IO to avoid sending unexpected messages to STDOUT
6
+ YARD::Logger.instance.io = File.new(File::NULL, 'w')
7
+
5
8
  module Solargraph
6
9
  # A placeholder for the @!domain directive. It doesn't need to do anything
7
10
  # for yardocs. It's only used for Solargraph API maps.
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.3
4
+ version: 0.43.2
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-06-14 00:00:00.000000000 Z
11
+ date: 2021-09-23 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