solargraph 0.47.2 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/plugins.yml +40 -0
  4. data/.github/workflows/rspec.yml +4 -8
  5. data/.github/workflows/typecheck.yml +34 -0
  6. data/.yardopts +2 -2
  7. data/CHANGELOG.md +166 -3
  8. data/LICENSE +1 -1
  9. data/README.md +19 -16
  10. data/SPONSORS.md +2 -9
  11. data/lib/solargraph/api_map/cache.rb +50 -20
  12. data/lib/solargraph/api_map/source_to_yard.rb +17 -10
  13. data/lib/solargraph/api_map/store.rb +68 -15
  14. data/lib/solargraph/api_map.rb +238 -112
  15. data/lib/solargraph/bench.rb +3 -2
  16. data/lib/solargraph/cache.rb +77 -0
  17. data/lib/solargraph/complex_type/type_methods.rb +116 -35
  18. data/lib/solargraph/complex_type/unique_type.rb +261 -33
  19. data/lib/solargraph/complex_type.rb +149 -30
  20. data/lib/solargraph/convention/rakefile.rb +17 -0
  21. data/lib/solargraph/convention.rb +2 -3
  22. data/lib/solargraph/converters/dd.rb +5 -0
  23. data/lib/solargraph/converters/dl.rb +3 -0
  24. data/lib/solargraph/converters/dt.rb +3 -0
  25. data/lib/solargraph/diagnostics/rubocop.rb +23 -8
  26. data/lib/solargraph/diagnostics/rubocop_helpers.rb +4 -1
  27. data/lib/solargraph/diagnostics/type_check.rb +1 -0
  28. data/lib/solargraph/diagnostics.rb +2 -2
  29. data/lib/solargraph/doc_map.rb +187 -0
  30. data/lib/solargraph/gem_pins.rb +72 -0
  31. data/lib/solargraph/language_server/host/diagnoser.rb +2 -2
  32. data/lib/solargraph/language_server/host/dispatch.rb +22 -5
  33. data/lib/solargraph/language_server/host/message_worker.rb +4 -0
  34. data/lib/solargraph/language_server/host/sources.rb +8 -65
  35. data/lib/solargraph/language_server/host.rb +88 -93
  36. data/lib/solargraph/language_server/message/base.rb +1 -1
  37. data/lib/solargraph/language_server/message/completion_item/resolve.rb +3 -1
  38. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +13 -1
  39. data/lib/solargraph/language_server/message/extended/download_core.rb +1 -5
  40. data/lib/solargraph/language_server/message/initialize.rb +27 -0
  41. data/lib/solargraph/language_server/message/initialized.rb +1 -0
  42. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +4 -1
  43. data/lib/solargraph/language_server/message/text_document/formatting.rb +5 -4
  44. data/lib/solargraph/language_server/message/text_document/hover.rb +2 -0
  45. data/lib/solargraph/language_server/message/text_document/signature_help.rb +1 -6
  46. data/lib/solargraph/language_server/message/text_document/type_definition.rb +24 -0
  47. data/lib/solargraph/language_server/message/text_document.rb +1 -1
  48. data/lib/solargraph/language_server/message/workspace/did_change_configuration.rb +5 -0
  49. data/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +10 -3
  50. data/lib/solargraph/language_server/message.rb +1 -0
  51. data/lib/solargraph/language_server/progress.rb +118 -0
  52. data/lib/solargraph/language_server/transport/adapter.rb +16 -1
  53. data/lib/solargraph/language_server/transport/data_reader.rb +2 -0
  54. data/lib/solargraph/language_server.rb +1 -0
  55. data/lib/solargraph/library.rb +231 -104
  56. data/lib/solargraph/location.rb +1 -0
  57. data/lib/solargraph/page.rb +6 -0
  58. data/lib/solargraph/parser/comment_ripper.rb +4 -0
  59. data/lib/solargraph/parser/node_methods.rb +47 -7
  60. data/lib/solargraph/parser/node_processor/base.rb +11 -1
  61. data/lib/solargraph/parser/node_processor.rb +1 -0
  62. data/lib/solargraph/parser/{legacy → parser_gem}/class_methods.rb +31 -9
  63. data/lib/solargraph/parser/{legacy → parser_gem}/flawed_builder.rb +3 -1
  64. data/lib/solargraph/parser/{legacy → parser_gem}/node_chainer.rb +57 -41
  65. data/lib/solargraph/parser/parser_gem/node_methods.rb +495 -0
  66. data/lib/solargraph/parser/{rubyvm → parser_gem}/node_processors/alias_node.rb +1 -1
  67. data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +53 -0
  68. data/lib/solargraph/parser/{rubyvm → parser_gem}/node_processors/begin_node.rb +1 -1
  69. data/lib/solargraph/parser/{legacy → parser_gem}/node_processors/block_node.rb +3 -2
  70. data/lib/solargraph/parser/{legacy → parser_gem}/node_processors/casgn_node.rb +14 -4
  71. data/lib/solargraph/parser/{rubyvm → parser_gem}/node_processors/cvasgn_node.rb +1 -1
  72. data/lib/solargraph/parser/{rubyvm → parser_gem}/node_processors/def_node.rb +7 -20
  73. data/lib/solargraph/parser/{legacy → parser_gem}/node_processors/defs_node.rb +2 -2
  74. data/lib/solargraph/parser/{legacy → parser_gem}/node_processors/gvasgn_node.rb +1 -1
  75. data/lib/solargraph/parser/{legacy → parser_gem}/node_processors/ivasgn_node.rb +2 -2
  76. data/lib/solargraph/parser/{legacy → parser_gem}/node_processors/lvasgn_node.rb +2 -2
  77. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +47 -0
  78. data/lib/solargraph/parser/{legacy → parser_gem}/node_processors/namespace_node.rb +2 -2
  79. data/lib/solargraph/parser/{legacy → parser_gem}/node_processors/orasgn_node.rb +1 -1
  80. data/lib/solargraph/parser/{legacy → parser_gem}/node_processors/resbody_node.rb +3 -3
  81. data/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +42 -0
  82. data/lib/solargraph/parser/{legacy → parser_gem}/node_processors/send_node.rb +7 -5
  83. data/lib/solargraph/parser/{legacy → parser_gem}/node_processors/sym_node.rb +1 -1
  84. data/lib/solargraph/parser/parser_gem/node_processors.rb +56 -0
  85. data/lib/solargraph/parser/parser_gem.rb +12 -0
  86. data/lib/solargraph/parser/region.rb +1 -1
  87. data/lib/solargraph/parser/snippet.rb +2 -0
  88. data/lib/solargraph/parser.rb +9 -10
  89. data/lib/solargraph/pin/base.rb +69 -11
  90. data/lib/solargraph/pin/base_variable.rb +40 -7
  91. data/lib/solargraph/pin/block.rb +81 -33
  92. data/lib/solargraph/pin/closure.rb +17 -2
  93. data/lib/solargraph/pin/common.rb +7 -3
  94. data/lib/solargraph/pin/conversions.rb +34 -8
  95. data/lib/solargraph/pin/delegated_method.rb +101 -0
  96. data/lib/solargraph/pin/documenting.rb +25 -32
  97. data/lib/solargraph/pin/instance_variable.rb +4 -0
  98. data/lib/solargraph/pin/local_variable.rb +13 -1
  99. data/lib/solargraph/pin/method.rb +273 -17
  100. data/lib/solargraph/pin/namespace.rb +17 -1
  101. data/lib/solargraph/pin/parameter.rb +40 -28
  102. data/lib/solargraph/pin/reference/override.rb +2 -2
  103. data/lib/solargraph/pin/reference.rb +8 -0
  104. data/lib/solargraph/pin/search.rb +4 -4
  105. data/lib/solargraph/pin/signature.rb +143 -0
  106. data/lib/solargraph/pin.rb +2 -1
  107. data/lib/solargraph/range.rb +4 -6
  108. data/lib/solargraph/rbs_map/conversions.rb +607 -0
  109. data/lib/solargraph/rbs_map/core_fills.rb +50 -0
  110. data/lib/solargraph/rbs_map/core_map.rb +28 -0
  111. data/lib/solargraph/rbs_map/stdlib_map.rb +33 -0
  112. data/lib/solargraph/rbs_map.rb +92 -0
  113. data/lib/solargraph/shell.rb +85 -59
  114. data/lib/solargraph/source/chain/array.rb +32 -0
  115. data/lib/solargraph/source/chain/block_symbol.rb +13 -0
  116. data/lib/solargraph/source/chain/call.rb +125 -61
  117. data/lib/solargraph/source/chain/constant.rb +15 -1
  118. data/lib/solargraph/source/chain/if.rb +23 -0
  119. data/lib/solargraph/source/chain/link.rb +8 -2
  120. data/lib/solargraph/source/chain/or.rb +1 -1
  121. data/lib/solargraph/source/chain/z_super.rb +3 -3
  122. data/lib/solargraph/source/chain.rb +64 -14
  123. data/lib/solargraph/source/change.rb +3 -0
  124. data/lib/solargraph/source/cursor.rb +2 -0
  125. data/lib/solargraph/source/source_chainer.rb +8 -5
  126. data/lib/solargraph/source/updater.rb +1 -0
  127. data/lib/solargraph/source.rb +18 -63
  128. data/lib/solargraph/source_map/clip.rb +31 -23
  129. data/lib/solargraph/source_map/mapper.rb +23 -7
  130. data/lib/solargraph/source_map.rb +36 -11
  131. data/lib/solargraph/type_checker/checks.rb +10 -2
  132. data/lib/solargraph/type_checker.rb +229 -100
  133. data/lib/solargraph/version.rb +1 -1
  134. data/lib/solargraph/views/environment.erb +2 -2
  135. data/lib/solargraph/workspace/config.rb +15 -11
  136. data/lib/solargraph/workspace.rb +41 -17
  137. data/lib/solargraph/yard_map/cache.rb +6 -0
  138. data/lib/solargraph/yard_map/helpers.rb +1 -1
  139. data/lib/solargraph/yard_map/mapper/to_method.rb +23 -7
  140. data/lib/solargraph/yard_map/mapper.rb +1 -1
  141. data/lib/solargraph/yard_map/to_method.rb +11 -4
  142. data/lib/solargraph/yard_map.rb +1 -443
  143. data/lib/solargraph/yard_tags.rb +20 -0
  144. data/lib/solargraph/yardoc.rb +52 -0
  145. data/lib/solargraph.rb +8 -6
  146. data/solargraph.gemspec +19 -8
  147. metadata +164 -99
  148. data/.travis.yml +0 -19
  149. data/lib/solargraph/api_map/bundler_methods.rb +0 -22
  150. data/lib/solargraph/compat.rb +0 -37
  151. data/lib/solargraph/convention/rspec.rb +0 -30
  152. data/lib/solargraph/documentor.rb +0 -76
  153. data/lib/solargraph/language_server/host/cataloger.rb +0 -56
  154. data/lib/solargraph/parser/legacy/node_methods.rb +0 -325
  155. data/lib/solargraph/parser/legacy/node_processors/alias_node.rb +0 -23
  156. data/lib/solargraph/parser/legacy/node_processors/args_node.rb +0 -35
  157. data/lib/solargraph/parser/legacy/node_processors/begin_node.rb +0 -15
  158. data/lib/solargraph/parser/legacy/node_processors/cvasgn_node.rb +0 -23
  159. data/lib/solargraph/parser/legacy/node_processors/def_node.rb +0 -63
  160. data/lib/solargraph/parser/legacy/node_processors/sclass_node.rb +0 -21
  161. data/lib/solargraph/parser/legacy/node_processors.rb +0 -54
  162. data/lib/solargraph/parser/legacy.rb +0 -12
  163. data/lib/solargraph/parser/rubyvm/class_methods.rb +0 -144
  164. data/lib/solargraph/parser/rubyvm/node_chainer.rb +0 -160
  165. data/lib/solargraph/parser/rubyvm/node_methods.rb +0 -315
  166. data/lib/solargraph/parser/rubyvm/node_processors/args_node.rb +0 -85
  167. data/lib/solargraph/parser/rubyvm/node_processors/block_node.rb +0 -42
  168. data/lib/solargraph/parser/rubyvm/node_processors/casgn_node.rb +0 -22
  169. data/lib/solargraph/parser/rubyvm/node_processors/defs_node.rb +0 -57
  170. data/lib/solargraph/parser/rubyvm/node_processors/gvasgn_node.rb +0 -23
  171. data/lib/solargraph/parser/rubyvm/node_processors/ivasgn_node.rb +0 -38
  172. data/lib/solargraph/parser/rubyvm/node_processors/kw_arg_node.rb +0 -39
  173. data/lib/solargraph/parser/rubyvm/node_processors/lit_node.rb +0 -20
  174. data/lib/solargraph/parser/rubyvm/node_processors/lvasgn_node.rb +0 -27
  175. data/lib/solargraph/parser/rubyvm/node_processors/namespace_node.rb +0 -39
  176. data/lib/solargraph/parser/rubyvm/node_processors/opt_arg_node.rb +0 -26
  177. data/lib/solargraph/parser/rubyvm/node_processors/orasgn_node.rb +0 -15
  178. data/lib/solargraph/parser/rubyvm/node_processors/resbody_node.rb +0 -45
  179. data/lib/solargraph/parser/rubyvm/node_processors/sclass_node.rb +0 -21
  180. data/lib/solargraph/parser/rubyvm/node_processors/scope_node.rb +0 -15
  181. data/lib/solargraph/parser/rubyvm/node_processors/send_node.rb +0 -277
  182. data/lib/solargraph/parser/rubyvm/node_processors/sym_node.rb +0 -18
  183. data/lib/solargraph/parser/rubyvm/node_processors.rb +0 -63
  184. data/lib/solargraph/parser/rubyvm.rb +0 -40
  185. data/lib/solargraph/yard_map/core_docs.rb +0 -170
  186. data/lib/solargraph/yard_map/core_fills.rb +0 -208
  187. data/lib/solargraph/yard_map/core_gen.rb +0 -76
  188. data/lib/solargraph/yard_map/rdoc_to_yard.rb +0 -140
  189. data/lib/solargraph/yard_map/stdlib_fills.rb +0 -43
  190. data/lib/yard-solargraph.rb +0 -33
  191. data/yardoc/2.2.2.tar.gz +0 -0
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'pathname'
4
+ require 'observer'
4
5
 
5
6
  module Solargraph
6
7
  # A Library handles coordination between a Workspace and an ApiMap.
7
8
  #
8
9
  class Library
9
10
  include Logging
11
+ include Observable
10
12
 
11
13
  # @return [Solargraph::Workspace]
12
14
  attr_reader :workspace
@@ -17,12 +19,19 @@ module Solargraph
17
19
  # @return [Source, nil]
18
20
  attr_reader :current
19
21
 
22
+ # @return [LanguageServer::Progress, nil]
23
+ attr_reader :cache_progress
24
+
20
25
  # @param workspace [Solargraph::Workspace]
21
26
  # @param name [String, nil]
22
27
  def initialize workspace = Solargraph::Workspace.new, name = nil
23
28
  @workspace = workspace
24
29
  @name = name
25
- @synchronized = false
30
+ @threads = []
31
+ # @type [Integer, nil]
32
+ @total = nil
33
+ # @type [Source, nil]
34
+ @current = nil
26
35
  end
27
36
 
28
37
  def inspect
@@ -35,7 +44,7 @@ module Solargraph
35
44
  #
36
45
  # @return [Boolean]
37
46
  def synchronized?
38
- @synchronized
47
+ !mutex.locked?
39
48
  end
40
49
 
41
50
  # Attach a source to the library.
@@ -47,17 +56,15 @@ module Solargraph
47
56
  # @param source [Source, nil]
48
57
  # @return [void]
49
58
  def attach source
50
- mutex.synchronize do
51
- if @current && (!source || @current.filename != source.filename) && source_map_hash.key?(@current.filename) && !workspace.has_file?(@current.filename)
52
- source_map_hash.delete @current.filename
53
- source_map_external_require_hash.delete @current.filename
54
- @external_requires = nil
55
- @synchronized = false
56
- end
57
- @current = source
58
- maybe_map @current
59
- catalog_inlock
59
+ if @current && (!source || @current.filename != source.filename) && source_map_hash.key?(@current.filename) && !workspace.has_file?(@current.filename)
60
+ source_map_hash.delete @current.filename
61
+ source_map_external_require_hash.delete @current.filename
62
+ @external_requires = nil
60
63
  end
64
+ changed = source && @current != source
65
+ @current = source
66
+ maybe_map @current
67
+ catalog if changed
61
68
  end
62
69
 
63
70
  # True if the specified file is currently attached.
@@ -95,47 +102,37 @@ module Solargraph
95
102
  # @param text [String] The contents of the file
96
103
  # @return [Boolean] True if the file was added to the workspace.
97
104
  def create filename, text
98
- result = false
99
- mutex.synchronize do
100
- next unless contain?(filename) || open?(filename) || workspace.would_merge?(filename)
101
- @synchronized = false
102
- source = Solargraph::Source.load_string(text, filename)
103
- workspace.merge(source)
104
- result = true
105
- end
106
- result
105
+ return false unless contain?(filename) || open?(filename)
106
+ source = Solargraph::Source.load_string(text, filename)
107
+ workspace.merge(source)
108
+ true
107
109
  end
108
110
 
109
- # Create a file source from a file on disk. The file is ignored if it is
111
+ # Create file sources from files on disk. A file is ignored if it is
110
112
  # neither open in the library nor included in the workspace.
111
113
  #
112
- # @param filename [String]
113
- # @return [Boolean] True if the file was added to the workspace.
114
- def create_from_disk filename
115
- result = false
116
- mutex.synchronize do
117
- next if File.directory?(filename) || !File.exist?(filename)
118
- next unless contain?(filename) || open?(filename) || workspace.would_merge?(filename)
119
- source = Solargraph::Source.load_string(File.read(filename), filename)
120
- workspace.merge(source)
121
- maybe_map source
122
- result = true
123
- end
114
+ # @param filenames [Array<String>]
115
+ # @return [Boolean] True if at least one file was added to the workspace.
116
+ def create_from_disk *filenames
117
+ sources = filenames
118
+ .reject { |filename| File.directory?(filename) || !File.exist?(filename) }
119
+ .map { |filename| Solargraph::Source.load_string(File.read(filename), filename) }
120
+ result = workspace.merge(*sources)
121
+ sources.each { |source| maybe_map source }
124
122
  result
125
123
  end
126
124
 
127
- # Delete a file from the library. Deleting a file will make it unavailable
125
+ # Delete files from the library. Deleting a file will make it unavailable
128
126
  # for checkout and optionally remove it from the workspace unless the
129
127
  # workspace configuration determines that it should still exist.
130
128
  #
131
- # @param filename [String]
132
- # @return [Boolean] True if the file was deleted
133
- def delete filename
134
- detach filename
129
+ # @param filenames [Array<String>]
130
+ # @return [Boolean] True if any file was deleted
131
+ def delete *filenames
135
132
  result = false
136
- mutex.synchronize do
137
- result = workspace.remove(filename)
138
- @synchronized = !result if synchronized?
133
+ filenames.each do |filename|
134
+ detach filename
135
+ result ||= workspace.remove(filename)
139
136
  end
140
137
  result
141
138
  end
@@ -146,11 +143,10 @@ module Solargraph
146
143
  # @param filename [String]
147
144
  # @return [void]
148
145
  def close filename
149
- mutex.synchronize do
150
- @synchronized = false
151
- @current = nil if @current && @current.filename == filename
152
- catalog
153
- end
146
+ return unless @current&.filename == filename
147
+
148
+ @current = nil
149
+ catalog unless workspace.has_file?(filename)
154
150
  end
155
151
 
156
152
  # Get completion suggestions at the specified file and location.
@@ -158,12 +154,13 @@ module Solargraph
158
154
  # @param filename [String] The file to analyze
159
155
  # @param line [Integer] The zero-based line number
160
156
  # @param column [Integer] The zero-based column number
161
- # @return [SourceMap::Completion]
157
+ # @return [SourceMap::Completion, nil]
162
158
  # @todo Take a Location instead of filename/line/column
163
159
  def completions_at filename, line, column
160
+ sync_catalog
164
161
  position = Position.new(line, column)
165
162
  cursor = Source::Cursor.new(read(filename), position)
166
- api_map.clip(cursor).complete
163
+ mutex.synchronize { api_map.clip(cursor).complete }
167
164
  rescue FileNotFoundError => e
168
165
  handle_file_not_found filename, e
169
166
  end
@@ -174,11 +171,12 @@ module Solargraph
174
171
  # @param filename [String] The file to analyze
175
172
  # @param line [Integer] The zero-based line number
176
173
  # @param column [Integer] The zero-based column number
177
- # @return [Array<Solargraph::Pin::Base>]
174
+ # @return [Array<Solargraph::Pin::Base>, nil]
178
175
  # @todo Take filename/position instead of filename/line/column
179
176
  def definitions_at filename, line, column
180
177
  position = Position.new(line, column)
181
178
  cursor = Source::Cursor.new(read(filename), position)
179
+ sync_catalog
182
180
  if cursor.comment?
183
181
  source = read(filename)
184
182
  offset = Solargraph::Position.to_offset(source.code, Solargraph::Position.new(line, column))
@@ -186,18 +184,35 @@ module Solargraph
186
184
  rgt = source.code[offset..-1].match(/^([a-z0-9_]*)(:[a-z0-9_:]*)?[\]>, ]/i)
187
185
  if lft && rgt
188
186
  tag = (lft[1] + rgt[1]).sub(/:+$/, '')
189
- clip = api_map.clip(cursor)
187
+ clip = mutex.synchronize { api_map.clip(cursor) }
190
188
  clip.translate tag
191
189
  else
192
190
  []
193
191
  end
194
192
  else
195
- api_map.clip(cursor).define.map { |pin| pin.realize(api_map) }
193
+ mutex.synchronize { api_map.clip(cursor).define.map { |pin| pin.realize(api_map) } }
196
194
  end
197
195
  rescue FileNotFoundError => e
198
196
  handle_file_not_found(filename, e)
199
197
  end
200
198
 
199
+ # Get type definition suggestions for the expression at the specified file and
200
+ # location.
201
+ #
202
+ # @param filename [String] The file to analyze
203
+ # @param line [Integer] The zero-based line number
204
+ # @param column [Integer] The zero-based column number
205
+ # @return [Array<Solargraph::Pin::Base>, nil]
206
+ # @todo Take filename/position instead of filename/line/column
207
+ def type_definitions_at filename, line, column
208
+ position = Position.new(line, column)
209
+ cursor = Source::Cursor.new(read(filename), position)
210
+ sync_catalog
211
+ mutex.synchronize { api_map.clip(cursor).types }
212
+ rescue FileNotFoundError => e
213
+ handle_file_not_found filename, e
214
+ end
215
+
201
216
  # Get signature suggestions for the method at the specified file and
202
217
  # location.
203
218
  #
@@ -209,7 +224,8 @@ module Solargraph
209
224
  def signatures_at filename, line, column
210
225
  position = Position.new(line, column)
211
226
  cursor = Source::Cursor.new(read(filename), position)
212
- api_map.clip(cursor).signify
227
+ sync_catalog
228
+ mutex.synchronize { api_map.clip(cursor).signify }
213
229
  end
214
230
 
215
231
  # @param filename [String]
@@ -220,8 +236,9 @@ module Solargraph
220
236
  # @return [Array<Solargraph::Range>]
221
237
  # @todo Take a Location instead of filename/line/column
222
238
  def references_from filename, line, column, strip: false, only: false
223
- cursor = api_map.cursor_at(filename, Position.new(line, column))
224
- clip = api_map.clip(cursor)
239
+ sync_catalog
240
+ cursor = Source::Cursor.new(read(filename), [line, column])
241
+ clip = mutex.synchronize { api_map.clip(cursor) }
225
242
  pin = clip.define.first
226
243
  return [] unless pin
227
244
  result = []
@@ -234,7 +251,19 @@ module Solargraph
234
251
  found = source.references(pin.name)
235
252
  found.select! do |loc|
236
253
  referenced = definitions_at(loc.filename, loc.range.ending.line, loc.range.ending.character).first
237
- referenced && referenced.path == pin.path
254
+ referenced&.path == pin.path
255
+ end
256
+ if pin.path == 'Class#new'
257
+ caller = cursor.chain.base.infer(api_map, clip.send(:block), clip.locals).first
258
+ if caller.defined?
259
+ found.select! do |loc|
260
+ clip = api_map.clip_at(loc.filename, loc.range.start)
261
+ other = clip.send(:cursor).chain.base.infer(api_map, clip.send(:block), clip.locals).first
262
+ caller == other
263
+ end
264
+ else
265
+ found.clear
266
+ end
238
267
  end
239
268
  # HACK: for language clients that exclude special characters from the start of variable names
240
269
  if strip && match = cursor.word.match(/^[^a-z0-9_]+/i)
@@ -254,20 +283,31 @@ module Solargraph
254
283
  # @param location [Location]
255
284
  # @return [Array<Solargraph::Pin::Base>]
256
285
  def locate_pins location
257
- api_map.locate_pins(location).map { |pin| pin.realize(api_map) }
286
+ sync_catalog
287
+ mutex.synchronize { api_map.locate_pins(location).map { |pin| pin.realize(api_map) } }
258
288
  end
259
289
 
290
+ # Match a require reference to a file.
291
+ #
292
+ # @param location [Location]
293
+ # @return [Location, nil]
260
294
  def locate_ref location
261
295
  map = source_map_hash[location.filename]
262
296
  return if map.nil?
263
297
  pin = map.requires.select { |p| p.location.range.contain?(location.range.start) }.first
264
298
  return nil if pin.nil?
299
+ # @param full [String]
300
+ return_if_match = proc do |full|
301
+ if source_map_hash.key?(full)
302
+ return Location.new(full, Solargraph::Range.from_to(0, 0, 0, 0))
303
+ end
304
+ end
265
305
  workspace.require_paths.each do |path|
266
- full = Pathname.new(path).join("#{pin.name}.rb").to_s
267
- next unless source_map_hash.key?(full)
268
- return Location.new(full, Solargraph::Range.from_to(0, 0, 0, 0))
306
+ full = File.join path, pin.name
307
+ return_if_match.(full)
308
+ return_if_match.(full << ".rb")
269
309
  end
270
- api_map.yard_map.require_reference(pin.name)
310
+ nil
271
311
  rescue FileNotFoundError
272
312
  nil
273
313
  end
@@ -275,21 +315,24 @@ module Solargraph
275
315
  # Get an array of pins that match a path.
276
316
  #
277
317
  # @param path [String]
278
- # @return [Array<Solargraph::Pin::Base>]
318
+ # @return [Enumerable<Solargraph::Pin::Base>]
279
319
  def get_path_pins path
280
- api_map.get_path_suggestions(path)
320
+ sync_catalog
321
+ mutex.synchronize { api_map.get_path_suggestions(path) }
281
322
  end
282
323
 
283
324
  # @param query [String]
284
- # @return [Array<YARD::CodeObjects::Base>]
325
+ # @return [Enumerable<YARD::CodeObjects::Base>]
285
326
  def document query
286
- api_map.document query
327
+ sync_catalog
328
+ mutex.synchronize { api_map.document query }
287
329
  end
288
330
 
289
331
  # @param query [String]
290
332
  # @return [Array<String>]
291
333
  def search query
292
- api_map.search query
334
+ sync_catalog
335
+ mutex.synchronize { api_map.search query }
293
336
  end
294
337
 
295
338
  # Get an array of all symbols in the workspace that match the query.
@@ -297,7 +340,8 @@ module Solargraph
297
340
  # @param query [String]
298
341
  # @return [Array<Pin::Base>]
299
342
  def query_symbols query
300
- api_map.query_symbols query
343
+ sync_catalog
344
+ mutex.synchronize { api_map.query_symbols query }
301
345
  end
302
346
 
303
347
  # Get an array of document symbols.
@@ -309,15 +353,18 @@ module Solargraph
309
353
  # @param filename [String]
310
354
  # @return [Array<Solargraph::Pin::Base>]
311
355
  def document_symbols filename
312
- api_map.document_symbols(filename)
356
+ sync_catalog
357
+ mutex.synchronize { api_map.document_symbols(filename) }
313
358
  end
314
359
 
315
360
  # @param path [String]
316
- # @return [Array<Solargraph::Pin::Base>]
361
+ # @return [Enumerable<Solargraph::Pin::Base>]
317
362
  def path_pins path
318
- api_map.get_path_suggestions(path)
363
+ sync_catalog
364
+ mutex.synchronize { api_map.get_path_suggestions(path) }
319
365
  end
320
366
 
367
+ # @return [Array<SourceMap>]
321
368
  def source_maps
322
369
  source_map_hash.values
323
370
  end
@@ -340,10 +387,10 @@ module Solargraph
340
387
  # everything in the workspace should get diagnosed, or if there should
341
388
  # be an option to do so.
342
389
  #
390
+ sync_catalog
343
391
  return [] unless open?(filename)
344
392
  result = []
345
393
  source = read(filename)
346
- catalog
347
394
  repargs = {}
348
395
  workspace.config.reporters.each do |line|
349
396
  if line == 'all!'
@@ -369,19 +416,23 @@ module Solargraph
369
416
  #
370
417
  # @return [void]
371
418
  def catalog
372
- mutex.synchronize do
373
- catalog_inlock
374
- end
375
- end
376
-
377
- private def catalog_inlock
378
- return if synchronized?
379
- logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}"
380
- api_map.catalog bench
381
- @synchronized = true
382
- logger.info "Catalog complete (#{api_map.source_maps.length} files, #{api_map.pins.length} pins)" if logger.info?
419
+ @threads.delete_if(&:stop?)
420
+ @threads.push(Thread.new do
421
+ sleep 0.05 if RUBY_PLATFORM =~ /mingw/
422
+ next unless @threads.last == Thread.current
423
+
424
+ mutex.synchronize do
425
+ logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}"
426
+ api_map.catalog bench
427
+ logger.info "Catalog complete (#{api_map.source_maps.length} files, #{api_map.pins.length} pins)"
428
+ logger.info "#{api_map.uncached_gemspecs.length} uncached gemspecs"
429
+ cache_next_gemspec
430
+ end
431
+ end)
432
+ @threads.last.run if RUBY_PLATFORM =~ /mingw/
383
433
  end
384
434
 
435
+ # @return [Bench]
385
436
  def bench
386
437
  Bench.new(
387
438
  source_maps: source_map_hash.values,
@@ -417,15 +468,12 @@ module Solargraph
417
468
  # @return [Boolean] True if the source was merged into the workspace.
418
469
  def merge source
419
470
  Logging.logger.debug "Merging source: #{source.filename}"
420
- result = false
421
- mutex.synchronize do
422
- result = workspace.merge(source)
423
- maybe_map source
424
- end
425
- # catalog
471
+ result = workspace.merge(source)
472
+ maybe_map source
426
473
  result
427
474
  end
428
475
 
476
+ # @return [Hash{String => SourceMap}]
429
477
  def source_map_hash
430
478
  @source_map_hash ||= {}
431
479
  end
@@ -434,22 +482,21 @@ module Solargraph
434
482
  (workspace.filenames - source_map_hash.keys).empty?
435
483
  end
436
484
 
485
+ # @return [SourceMap, Boolean]
437
486
  def next_map
438
487
  return false if mapped?
439
- mutex.synchronize do
440
- @synchronized = false
441
- src = workspace.sources.find { |s| !source_map_hash.key?(s.filename) }
442
- if src
443
- Logging.logger.debug "Mapping #{src.filename}"
444
- source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
445
- find_external_requires(source_map_hash[src.filename])
446
- source_map_hash[src.filename]
447
- else
448
- false
449
- end
488
+ src = workspace.sources.find { |s| !source_map_hash.key?(s.filename) }
489
+ if src
490
+ Logging.logger.debug "Mapping #{src.filename}"
491
+ source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
492
+ find_external_requires(source_map_hash[src.filename])
493
+ source_map_hash[src.filename]
494
+ else
495
+ false
450
496
  end
451
497
  end
452
498
 
499
+ # @return [self]
453
500
  def map!
454
501
  workspace.sources.each do |src|
455
502
  source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
@@ -458,28 +505,34 @@ module Solargraph
458
505
  self
459
506
  end
460
507
 
508
+ # @return [Array<Solargraph::Pin::Base>]
461
509
  def pins
462
510
  @pins ||= []
463
511
  end
464
512
 
513
+ # @return [Set<String>]
465
514
  def external_requires
466
515
  @external_requires ||= source_map_external_require_hash.values.flatten.to_set
467
516
  end
468
517
 
469
518
  private
470
519
 
520
+ # @return [Hash{String => Set<String>}]
471
521
  def source_map_external_require_hash
472
522
  @source_map_external_require_hash ||= {}
473
523
  end
474
524
 
475
525
  # @param source_map [SourceMap]
526
+ # @return [void]
476
527
  def find_external_requires source_map
477
528
  new_set = source_map.requires.map(&:name).to_set
478
529
  # return if new_set == source_map_external_require_hash[source_map.filename]
530
+ _filenames = nil
531
+ filenames = ->{ _filenames ||= workspace.filenames.to_set }
479
532
  source_map_external_require_hash[source_map.filename] = new_set.reject do |path|
480
533
  workspace.require_paths.any? do |base|
481
- full = Pathname.new(base).join("#{path}.rb").to_s
482
- workspace.filenames.include?(full)
534
+ full = File.join(base, path)
535
+ filenames[].include?(full) or filenames[].include?(full << ".rb")
483
536
  end
484
537
  end
485
538
  @external_requires = nil
@@ -509,6 +562,9 @@ module Solargraph
509
562
  workspace.source(filename)
510
563
  end
511
564
 
565
+ # @param filename [String]
566
+ # @param error [FileNotFoundError]
567
+ # @return [nil]
512
568
  def handle_file_not_found filename, error
513
569
  if workspace.source(filename)
514
570
  Solargraph.logger.debug "#{filename} is not cataloged in the ApiMap"
@@ -518,11 +574,13 @@ module Solargraph
518
574
  end
519
575
  end
520
576
 
577
+ # @param source [Source, nil]
578
+ # @return [void]
521
579
  def maybe_map source
522
580
  return unless source
523
581
  return unless @current == source || workspace.has_file?(source.filename)
524
582
  if source_map_hash.key?(source.filename)
525
- return if source_map_hash[source.filename].code == source.code &&
583
+ return if source_map_hash[source.filename].code == source.code &&
526
584
  source_map_hash[source.filename].source.synchronized? &&
527
585
  source.synchronized?
528
586
  if source.synchronized?
@@ -530,7 +588,6 @@ module Solargraph
530
588
  unless source_map_hash[source.filename].try_merge!(new_map)
531
589
  source_map_hash[source.filename] = new_map
532
590
  find_external_requires(source_map_hash[source.filename])
533
- @synchronized = false
534
591
  end
535
592
  else
536
593
  # @todo Smelly instance variable access
@@ -539,8 +596,78 @@ module Solargraph
539
596
  else
540
597
  source_map_hash[source.filename] = Solargraph::SourceMap.map(source)
541
598
  find_external_requires(source_map_hash[source.filename])
542
- @synchronized = false
543
599
  end
544
600
  end
601
+
602
+ # @return [Set<Gem::Specification>]
603
+ def cache_errors
604
+ @cache_errors ||= Set.new
605
+ end
606
+
607
+ # @return [void]
608
+ def cache_next_gemspec
609
+ return if @cache_progress
610
+ spec = api_map.uncached_gemspecs.find { |spec| !cache_errors.include?(spec) }
611
+ return end_cache_progress unless spec
612
+
613
+ pending = api_map.uncached_gemspecs.length - cache_errors.length - 1
614
+ logger.info "Caching #{spec.name} #{spec.version}"
615
+ Thread.new do
616
+ cache_pid = Process.spawn(workspace.command_path, 'cache', spec.name, spec.version.to_s)
617
+ report_cache_progress spec.name, pending
618
+ Process.wait(cache_pid)
619
+ logger.info "Cached #{spec.name} #{spec.version}"
620
+ rescue Errno::EINVAL => _e
621
+ logger.info "Cached #{spec.name} #{spec.version} with EINVAL"
622
+ rescue StandardError => e
623
+ cache_errors.add spec
624
+ Solargraph.logger.warn "Error caching gemspec #{spec.name} #{spec.version}: [#{e.class}] #{e.message}"
625
+ ensure
626
+ end_cache_progress
627
+ catalog
628
+ end
629
+ end
630
+
631
+ # @param gem_name [String]
632
+ # @param pending [Integer]
633
+ # @return [void]
634
+ def report_cache_progress gem_name, pending
635
+ @total ||= pending
636
+ @total = pending if pending > @total
637
+ finished = @total - pending
638
+ pct = if @total.zero?
639
+ 0
640
+ else
641
+ ((finished.to_f / @total.to_f) * 100).to_i
642
+ end
643
+ message = "#{gem_name}#{pending > 0 ? " (+#{pending})" : ''}"
644
+ # "
645
+ if @cache_progress
646
+ @cache_progress.report(message, pct)
647
+ else
648
+ @cache_progress = LanguageServer::Progress.new('Caching gem')
649
+ # If we don't send both a begin and a report, the progress notification
650
+ # might get stuck in the status bar forever
651
+ @cache_progress.begin(message, pct)
652
+ changed
653
+ notify_observers @cache_progress
654
+ @cache_progress.report(message, pct)
655
+ end
656
+ changed
657
+ notify_observers @cache_progress
658
+ end
659
+
660
+ # @return [void]
661
+ def end_cache_progress
662
+ changed if @cache_progress&.finish('done')
663
+ notify_observers @cache_progress
664
+ @cache_progress = nil
665
+ @total = nil
666
+ end
667
+
668
+ def sync_catalog
669
+ @threads.delete_if(&:stop?)
670
+ .last&.join
671
+ end
545
672
  end
546
673
  end
@@ -25,6 +25,7 @@ module Solargraph
25
25
  }
26
26
  end
27
27
 
28
+ # @param other [BasicObject]
28
29
  def == other
29
30
  return false unless other.is_a?(Location)
30
31
  filename == other.filename and range == other.range
@@ -7,6 +7,12 @@ require 'cgi'
7
7
 
8
8
  module Solargraph
9
9
  class Page
10
+ # @todo This method directive is necessary because OpenStruct.new confuses
11
+ # the typechecker.
12
+ # @!method self.new(locals, render_method)
13
+ # @param locals[Hash]
14
+ # @param render_method [Proc]
15
+ # @return [Binder]
10
16
  class Binder < OpenStruct
11
17
  # @param locals [Hash]
12
18
  # @param render_method [Proc]
@@ -3,6 +3,9 @@ require 'ripper'
3
3
  module Solargraph
4
4
  module Parser
5
5
  class CommentRipper < Ripper::SexpBuilderPP
6
+ # @param src [String]
7
+ # @param filename [String]
8
+ # @param lineno [Integer]
6
9
  def initialize src, filename = '(ripper)', lineno = 0
7
10
  super
8
11
  @buffer = src
@@ -42,6 +45,7 @@ module Solargraph
42
45
  result
43
46
  end
44
47
 
48
+ # @return [Hash{Integer => String}]
45
49
  def parse
46
50
  @comments = {}
47
51
  super