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