solargraph 0.42.3 → 0.43.2

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