solargraph 0.40.0 → 0.41.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -1
  3. data/CHANGELOG.md +33 -0
  4. data/README.md +15 -0
  5. data/SPONSORS.md +1 -0
  6. data/lib/.rubocop.yml +1 -1
  7. data/lib/solargraph.rb +8 -7
  8. data/lib/solargraph/api_map.rb +52 -75
  9. data/lib/solargraph/api_map/store.rb +5 -0
  10. data/lib/solargraph/bench.rb +16 -19
  11. data/lib/solargraph/compat.rb +15 -1
  12. data/lib/solargraph/diagnostics/rubocop.rb +10 -2
  13. data/lib/solargraph/diagnostics/rubocop_helpers.rb +19 -20
  14. data/lib/solargraph/language_server/host.rb +74 -1
  15. data/lib/solargraph/language_server/message/completion_item/resolve.rb +1 -0
  16. data/lib/solargraph/language_server/message/extended/environment.rb +3 -3
  17. data/lib/solargraph/language_server/message/initialize.rb +30 -35
  18. data/lib/solargraph/language_server/message/text_document/formatting.rb +69 -21
  19. data/lib/solargraph/language_server/message/text_document/hover.rb +1 -1
  20. data/lib/solargraph/library.rb +94 -24
  21. data/lib/solargraph/parser/legacy/node_methods.rb +9 -0
  22. data/lib/solargraph/parser/rubyvm/node_chainer.rb +0 -1
  23. data/lib/solargraph/parser/rubyvm/node_methods.rb +18 -1
  24. data/lib/solargraph/parser/rubyvm/node_processors/args_node.rb +11 -12
  25. data/lib/solargraph/parser/rubyvm/node_processors/opt_arg_node.rb +1 -6
  26. data/lib/solargraph/parser/rubyvm/node_processors/send_node.rb +1 -1
  27. data/lib/solargraph/source.rb +1 -1
  28. data/lib/solargraph/source/chain/head.rb +0 -16
  29. data/lib/solargraph/source/source_chainer.rb +1 -0
  30. data/lib/solargraph/source_map/mapper.rb +0 -5
  31. data/lib/solargraph/type_checker.rb +49 -39
  32. data/lib/solargraph/type_checker/checks.rb +9 -5
  33. data/lib/solargraph/type_checker/rules.rb +5 -1
  34. data/lib/solargraph/version.rb +1 -1
  35. data/lib/solargraph/workspace/config.rb +19 -3
  36. data/lib/solargraph/yard_map/core_fills.rb +1 -0
  37. data/solargraph.gemspec +1 -1
  38. metadata +4 -4
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'observer'
4
4
  require 'set'
5
+ require 'securerandom'
5
6
 
6
7
  module Solargraph
7
8
  module LanguageServer
@@ -192,7 +193,7 @@ module Solargraph
192
193
  def diagnose uri
193
194
  if sources.include?(uri)
194
195
  library = library_for(uri)
195
- if library.synchronized?
196
+ if library.mapped? && library.synchronized?
196
197
  logger.info "Diagnosing #{uri}"
197
198
  begin
198
199
  results = library.diagnose uri_to_file(uri)
@@ -277,6 +278,7 @@ module Solargraph
277
278
  begin
278
279
  lib = Solargraph::Library.load(path, name)
279
280
  libraries.push lib
281
+ async_library_map lib
280
282
  rescue WorkspaceTooLargeError => e
281
283
  send_notification 'window/showMessage', {
282
284
  'type' => Solargraph::LanguageServer::MessageTypes::WARNING,
@@ -494,6 +496,11 @@ module Solargraph
494
496
  library.read_text(filename)
495
497
  end
496
498
 
499
+ def formatter_config uri
500
+ library = library_for(uri)
501
+ library.workspace.config.formatter
502
+ end
503
+
497
504
  # @param uri [String]
498
505
  # @param line [Integer]
499
506
  # @param column [Integer]
@@ -626,6 +633,7 @@ module Solargraph
626
633
 
627
634
  # @return [void]
628
635
  def catalog
636
+ return unless libraries.all?(&:mapped?)
629
637
  libraries.each(&:catalog)
630
638
  end
631
639
 
@@ -736,6 +744,71 @@ module Solargraph
736
744
  def prepare_rename?
737
745
  client_capabilities['rename'] && client_capabilities['rename']['prepareSupport']
738
746
  end
747
+
748
+ def client_supports_progress?
749
+ client_capabilities['window'] && client_capabilities['window']['workDoneProgress']
750
+ end
751
+
752
+ # @param library [Library]
753
+ # @return [void]
754
+ def async_library_map library
755
+ return if library.mapped?
756
+ Thread.new do
757
+ if client_supports_progress?
758
+ uuid = SecureRandom.uuid
759
+ send_request 'window/workDoneProgress/create', {
760
+ token: uuid
761
+ } do |response|
762
+ do_async_library_map library, response.nil? ? uuid : nil
763
+ end
764
+ else
765
+ do_async_library_map library
766
+ end
767
+ end
768
+ end
769
+
770
+ def do_async_library_map library, uuid = nil
771
+ total = library.workspace.sources.length
772
+ if uuid
773
+ send_notification '$/progress', {
774
+ token: uuid,
775
+ value: {
776
+ kind: 'begin',
777
+ title: "Mapping workspace",
778
+ message: "0/#{total} files",
779
+ cancellable: false,
780
+ percentage: 0
781
+ }
782
+ }
783
+ end
784
+ pct = 0
785
+ mod = 10
786
+ while library.next_map
787
+ next unless uuid
788
+ cur = ((library.source_map_hash.keys.length.to_f / total.to_f) * 100).to_i
789
+ if cur > pct && cur % mod == 0
790
+ pct = cur
791
+ send_notification '$/progress', {
792
+ token: uuid,
793
+ value: {
794
+ kind: 'report',
795
+ cancellable: false,
796
+ message: "#{library.source_map_hash.keys.length}/#{total} files",
797
+ percentage: pct
798
+ }
799
+ }
800
+ end
801
+ end
802
+ if uuid
803
+ send_notification '$/progress', {
804
+ token: uuid,
805
+ value: {
806
+ kind: 'end',
807
+ message: 'Mapping complete'
808
+ }
809
+ }
810
+ end
811
+ end
739
812
  end
740
813
  end
741
814
  end
@@ -21,6 +21,7 @@ module Solargraph
21
21
  docs = pins
22
22
  .reject { |pin| pin.documentation.empty? && pin.return_type.undefined? }
23
23
  result = params
24
+ .transform_keys(&:to_sym)
24
25
  .merge(pins.first.resolve_completion_item)
25
26
  .merge(documentation: markup_content(join_docs(docs)))
26
27
  result[:detail] = pins.first.detail
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Make sure the environment page can report RuboCop's version
4
- require 'rubocop'
5
-
6
3
  module Solargraph
7
4
  module LanguageServer
8
5
  module Message
@@ -12,6 +9,9 @@ module Solargraph
12
9
  #
13
10
  class Environment < Base
14
11
  def process
12
+ # Make sure the environment page can report RuboCop's version
13
+ require 'rubocop'
14
+
15
15
  page = Solargraph::Page.new(host.options['viewsPath'])
16
16
  content = page.render('environment', layout: true, locals: { config: host.options, folders: host.folders })
17
17
  set_result(
@@ -1,49 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'benchmark'
4
-
5
3
  module Solargraph
6
4
  module LanguageServer
7
5
  module Message
8
6
  class Initialize < Base
9
7
  def process
10
- bm = Benchmark.measure {
11
- host.configure params['initializationOptions']
12
- host.client_capabilities = params['capabilities']
13
- if support_workspace_folders?
14
- host.prepare_folders params['workspaceFolders']
15
- elsif params['rootUri']
16
- host.prepare UriHelpers.uri_to_file(params['rootUri'])
17
- else
18
- host.prepare params['rootPath']
19
- end
20
- result = {
21
- capabilities: {
22
- textDocumentSync: 2, # @todo What should this be?
23
- workspace: {
24
- workspaceFolders: {
25
- supported: true,
26
- changeNotifications: true
27
- }
8
+ host.configure params['initializationOptions']
9
+ host.client_capabilities = params['capabilities']
10
+ if support_workspace_folders?
11
+ host.prepare_folders params['workspaceFolders']
12
+ elsif params['rootUri']
13
+ host.prepare UriHelpers.uri_to_file(params['rootUri'])
14
+ else
15
+ host.prepare params['rootPath']
16
+ end
17
+ result = {
18
+ capabilities: {
19
+ textDocumentSync: 2, # @todo What should this be?
20
+ workspace: {
21
+ workspaceFolders: {
22
+ supported: true,
23
+ changeNotifications: true
28
24
  }
29
25
  }
30
26
  }
31
- result[:capabilities].merge! static_completion unless dynamic_registration_for?('textDocument', 'completion')
32
- result[:capabilities].merge! static_signature_help unless dynamic_registration_for?('textDocument', 'signatureHelp')
33
- # result[:capabilities].merge! static_on_type_formatting unless dynamic_registration_for?('textDocument', 'onTypeFormatting')
34
- result[:capabilities].merge! static_hover unless dynamic_registration_for?('textDocument', 'hover')
35
- result[:capabilities].merge! static_document_formatting unless dynamic_registration_for?('textDocument', 'formatting')
36
- result[:capabilities].merge! static_document_symbols unless dynamic_registration_for?('textDocument', 'documentSymbol')
37
- result[:capabilities].merge! static_definitions unless dynamic_registration_for?('textDocument', 'definition')
38
- result[:capabilities].merge! static_rename unless dynamic_registration_for?('textDocument', 'rename')
39
- result[:capabilities].merge! static_references unless dynamic_registration_for?('textDocument', 'references')
40
- result[:capabilities].merge! static_workspace_symbols unless dynamic_registration_for?('workspace', 'symbol')
41
- result[:capabilities].merge! static_folding_range unless dynamic_registration_for?('textDocument', 'foldingRange')
42
- # @todo Temporarily disabled
43
- # result[:capabilities].merge! static_code_action unless dynamic_registration_for?('textDocument', 'codeAction')
44
- set_result result
45
27
  }
46
- Solargraph.logger.unknown "Solargraph initialized (#{bm.real} seconds)"
28
+ result[:capabilities].merge! static_completion unless dynamic_registration_for?('textDocument', 'completion')
29
+ result[:capabilities].merge! static_signature_help unless dynamic_registration_for?('textDocument', 'signatureHelp')
30
+ # result[:capabilities].merge! static_on_type_formatting unless dynamic_registration_for?('textDocument', 'onTypeFormatting')
31
+ result[:capabilities].merge! static_hover unless dynamic_registration_for?('textDocument', 'hover')
32
+ result[:capabilities].merge! static_document_formatting unless dynamic_registration_for?('textDocument', 'formatting')
33
+ result[:capabilities].merge! static_document_symbols unless dynamic_registration_for?('textDocument', 'documentSymbol')
34
+ result[:capabilities].merge! static_definitions unless dynamic_registration_for?('textDocument', 'definition')
35
+ result[:capabilities].merge! static_rename unless dynamic_registration_for?('textDocument', 'rename')
36
+ result[:capabilities].merge! static_references unless dynamic_registration_for?('textDocument', 'references')
37
+ result[:capabilities].merge! static_workspace_symbols unless dynamic_registration_for?('workspace', 'symbol')
38
+ result[:capabilities].merge! static_folding_range unless dynamic_registration_for?('textDocument', 'foldingRange')
39
+ # @todo Temporarily disabled
40
+ # result[:capabilities].merge! static_code_action unless dynamic_registration_for?('textDocument', 'codeAction')
41
+ set_result result
47
42
  end
48
43
 
49
44
  private
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rubocop'
4
3
  require 'securerandom'
4
+ require 'tmpdir'
5
5
 
6
6
  module Solargraph
7
7
  module LanguageServer
@@ -11,31 +11,79 @@ module Solargraph
11
11
  include Solargraph::Diagnostics::RubocopHelpers
12
12
 
13
13
  def process
14
- filename = uri_to_file(params['textDocument']['uri'])
15
- # Make the temp file in the original file's directory so RuboCop
16
- # detects the correct configuration
17
- # the .rb extension is needed for ruby file without extension, else rubocop won't format
18
- tempfile = File.join(File.dirname(filename), "_tmp_#{SecureRandom.hex(8)}_#{File.basename(filename)}.rb")
19
- rubocop_file = Diagnostics::RubocopHelpers.find_rubocop_file(filename)
20
- original = host.read_text(params['textDocument']['uri'])
21
- File.write tempfile, original
22
- begin
23
- args = ['-a', '-f', 'fi', tempfile]
24
- args.unshift('-c', fix_drive_letter(rubocop_file)) unless rubocop_file.nil?
25
- options, paths = RuboCop::Options.new.parse(args)
26
- store = RuboCop::ConfigStore.new
27
- redirect_stdout { RuboCop::Runner.new(options, store).run(paths) }
28
- result = File.read(tempfile)
29
- format original, result
30
- rescue RuboCop::ValidationError, RuboCop::ConfigNotFoundError => e
31
- set_error(Solargraph::LanguageServer::ErrorCodes::INTERNAL_ERROR, "[#{e.class}] #{e.message}")
32
- ensure
33
- File.unlink tempfile
14
+ file_uri = params['textDocument']['uri']
15
+ config = config_for(file_uri)
16
+ original = host.read_text(file_uri)
17
+ args = cli_args(file_uri, config)
18
+
19
+ require_rubocop(config['version'])
20
+ options, paths = RuboCop::Options.new.parse(args)
21
+ options[:stdin] = original
22
+ corrections = redirect_stdout do
23
+ RuboCop::Runner.new(options, RuboCop::ConfigStore.new).run(paths)
34
24
  end
25
+ result = options[:stdin]
26
+
27
+ log_corrections(corrections)
28
+
29
+ format original, result
30
+ rescue RuboCop::ValidationError, RuboCop::ConfigNotFoundError => e
31
+ set_error(Solargraph::LanguageServer::ErrorCodes::INTERNAL_ERROR, "[#{e.class}] #{e.message}")
35
32
  end
36
33
 
37
34
  private
38
35
 
36
+ def log_corrections(corrections)
37
+ corrections = corrections&.strip
38
+ return if corrections&.empty?
39
+
40
+ Solargraph.logger.info('Formatting result:')
41
+ corrections.each_line do |line|
42
+ next if line.strip.empty?
43
+ Solargraph.logger.info(line.strip)
44
+ end
45
+ end
46
+
47
+ def config_for(file_uri)
48
+ conf = host.formatter_config(file_uri)
49
+ return {} unless conf.is_a?(Hash)
50
+
51
+ conf['rubocop'] || {}
52
+ end
53
+
54
+ def cli_args file_uri, config
55
+ file = UriHelpers.uri_to_file(file_uri)
56
+ args = [
57
+ config['cops'] == 'all' ? '--auto-correct-all' : '--auto-correct',
58
+ '--cache', 'false',
59
+ '--format', formatter_class(config).name,
60
+ ]
61
+
62
+ ['except', 'only'].each do |arg|
63
+ cops = cop_list(config[arg])
64
+ args += ["--#{arg}", cops] if cops
65
+ end
66
+
67
+ args += config['extra_args'] if config['extra_args']
68
+ args + [file]
69
+ end
70
+
71
+ def formatter_class(config)
72
+ if self.class.const_defined?('BlankRubocopFormatter')
73
+ BlankRubocopFormatter
74
+ else
75
+ require_rubocop(config['version'])
76
+ klass = Class.new(::RuboCop::Formatter::BaseFormatter)
77
+ self.class.const_set 'BlankRubocopFormatter', klass
78
+ end
79
+ end
80
+
81
+ def cop_list(value)
82
+ value = value.join(',') if value.respond_to?(:join)
83
+ return nil if value == '' || !value.is_a?(String)
84
+ value
85
+ end
86
+
39
87
  # @param original [String]
40
88
  # @param result [String]
41
89
  # @return [void]
@@ -17,7 +17,7 @@ module Solargraph
17
17
  if !this_link.nil? && this_link != last_link
18
18
  parts.push this_link
19
19
  end
20
- parts.push pin.detail.gsub(':', '\\:') unless pin.is_a?(Pin::Namespace) || pin.detail.nil?
20
+ parts.push "`" + pin.detail.gsub(':', '\\:') + "`" unless pin.is_a?(Pin::Namespace) || pin.detail.nil?
21
21
  parts.push pin.documentation unless pin.documentation.nil? || pin.documentation.empty?
22
22
  unless parts.empty?
23
23
  data = parts.join("\n\n")
@@ -20,9 +20,7 @@ module Solargraph
20
20
  def initialize workspace = Solargraph::Workspace.new, name = nil
21
21
  @workspace = workspace
22
22
  @name = name
23
- api_map.catalog bench
24
- @synchronized = true
25
- @catalog_mutex = Mutex.new
23
+ @synchronized = false
26
24
  end
27
25
 
28
26
  def inspect
@@ -48,9 +46,13 @@ module Solargraph
48
46
  # @return [void]
49
47
  def attach source
50
48
  mutex.synchronize do
51
- @synchronized = (@current == source) if synchronized?
49
+ if @current && @current.filename != source.filename && source_map_hash.key?(@current.filename) && !workspace.has_file?(@current.filename)
50
+ source_map_hash.delete @current.filename
51
+ @synchronized = false
52
+ end
52
53
  @current = source
53
- catalog
54
+ maybe_map @current
55
+ api_map.catalog bench unless synchronized?
54
56
  end
55
57
  end
56
58
 
@@ -110,9 +112,9 @@ module Solargraph
110
112
  mutex.synchronize do
111
113
  next if File.directory?(filename) || !File.exist?(filename)
112
114
  next unless contain?(filename) || open?(filename) || workspace.would_merge?(filename)
113
- @synchronized = false
114
115
  source = Solargraph::Source.load_string(File.read(filename), filename)
115
116
  workspace.merge(source)
117
+ maybe_map source
116
118
  result = true
117
119
  end
118
120
  result
@@ -158,6 +160,8 @@ module Solargraph
158
160
  position = Position.new(line, column)
159
161
  cursor = Source::Cursor.new(read(filename), position)
160
162
  api_map.clip(cursor).complete
163
+ rescue FileNotFoundError => e
164
+ handle_file_not_found filename, e
161
165
  end
162
166
 
163
167
  # Get definition suggestions for the expression at the specified file and
@@ -186,6 +190,8 @@ module Solargraph
186
190
  else
187
191
  api_map.clip(cursor).define.map { |pin| pin.realize(api_map) }
188
192
  end
193
+ rescue FileNotFoundError => e
194
+ handle_file_not_found(filename, e)
189
195
  end
190
196
 
191
197
  # Get signature suggestions for the method at the specified file and
@@ -209,7 +215,6 @@ module Solargraph
209
215
  # @return [Array<Solargraph::Range>]
210
216
  # @todo Take a Location instead of filename/line/column
211
217
  def references_from filename, line, column, strip: false
212
- # checkout filename
213
218
  cursor = api_map.cursor_at(filename, Position.new(line, column))
214
219
  clip = api_map.clip(cursor)
215
220
  pins = clip.define
@@ -222,7 +227,7 @@ module Solargraph
222
227
  referenced = definitions_at(loc.filename, loc.range.ending.line, loc.range.ending.character)
223
228
  # HACK: The additional location comparison is necessary because
224
229
  # Clip#define can return proxies for parameter pins
225
- referenced.any?{|r| r == pin || r.location == pin.location}
230
+ referenced.any? { |r| r == pin || r.location == pin.location }
226
231
  end
227
232
  # HACK: for language clients that exclude special characters from the start of variable names
228
233
  if strip && match = cursor.word.match(/^[^a-z0-9_]+/i)
@@ -261,14 +266,12 @@ module Solargraph
261
266
  # @param query [String]
262
267
  # @return [Array<YARD::CodeObjects::Base>]
263
268
  def document query
264
- catalog
265
269
  api_map.document query
266
270
  end
267
271
 
268
272
  # @param query [String]
269
273
  # @return [Array<String>]
270
274
  def search query
271
- catalog
272
275
  api_map.search query
273
276
  end
274
277
 
@@ -277,7 +280,6 @@ module Solargraph
277
280
  # @param query [String]
278
281
  # @return [Array<Pin::Base>]
279
282
  def query_symbols query
280
- catalog
281
283
  api_map.query_symbols query
282
284
  end
283
285
 
@@ -290,17 +292,19 @@ module Solargraph
290
292
  # @param filename [String]
291
293
  # @return [Array<Solargraph::Pin::Base>]
292
294
  def document_symbols filename
293
- # checkout filename
294
295
  api_map.document_symbols(filename)
295
296
  end
296
297
 
297
298
  # @param path [String]
298
299
  # @return [Array<Solargraph::Pin::Base>]
299
300
  def path_pins path
300
- catalog
301
301
  api_map.get_path_suggestions(path)
302
302
  end
303
303
 
304
+ def source_maps
305
+ source_map_hash.values
306
+ end
307
+
304
308
  # Get the current text of a file in the library.
305
309
  #
306
310
  # @param filename [String]
@@ -320,7 +324,6 @@ module Solargraph
320
324
  # be an option to do so.
321
325
  #
322
326
  return [] unless open?(filename)
323
- catalog
324
327
  result = []
325
328
  source = read(filename)
326
329
  repargs = {}
@@ -348,7 +351,7 @@ module Solargraph
348
351
  #
349
352
  # @return [void]
350
353
  def catalog
351
- @catalog_mutex.synchronize do
354
+ mutex.synchronize do
352
355
  break if synchronized?
353
356
  logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}"
354
357
  api_map.catalog bench
@@ -357,6 +360,16 @@ module Solargraph
357
360
  end
358
361
  end
359
362
 
363
+ def bench
364
+ source_maps = @current ? [@current] : []
365
+ source_maps.concat source_map_hash.values
366
+ Bench.new(
367
+ source_maps: source_maps,
368
+ load_paths: workspace.require_paths,
369
+ gemnames: workspace.gemnames
370
+ )
371
+ end
372
+
360
373
  # Get an array of foldable ranges for the specified file.
361
374
  #
362
375
  # @deprecated The library should not need to handle folding ranges. The
@@ -383,14 +396,49 @@ module Solargraph
383
396
  # @param source [Source]
384
397
  # @return [Boolean] True if the source was merged into the workspace.
385
398
  def merge source
399
+ Logging.logger.debug "Merging source: #{source.filename}"
386
400
  result = false
387
401
  mutex.synchronize do
388
402
  result = workspace.merge(source)
389
- @synchronized = !result if synchronized?
403
+ maybe_map source
390
404
  end
405
+ # catalog
391
406
  result
392
407
  end
393
408
 
409
+ def source_map_hash
410
+ @source_map_hash ||= {}
411
+ end
412
+
413
+ def mapped?
414
+ (workspace.filenames - source_map_hash.keys).empty?
415
+ end
416
+
417
+ def next_map
418
+ return false if mapped?
419
+ mutex.synchronize do
420
+ @synchronized = false
421
+ src = workspace.sources.find { |s| !source_map_hash.key?(s.filename) }
422
+ if src
423
+ Logging.logger.debug "Mapping #{src.filename}"
424
+ source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
425
+ else
426
+ false
427
+ end
428
+ end
429
+ end
430
+
431
+ def map!
432
+ workspace.sources.each do |src|
433
+ source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
434
+ end
435
+ self
436
+ end
437
+
438
+ def pins
439
+ @pins ||= []
440
+ end
441
+
394
442
  private
395
443
 
396
444
  # @return [Mutex]
@@ -403,14 +451,6 @@ module Solargraph
403
451
  @api_map ||= Solargraph::ApiMap.new
404
452
  end
405
453
 
406
- # @return [Bench]
407
- def bench
408
- Bench.new(
409
- workspace: workspace,
410
- opened: @current ? [@current] : []
411
- )
412
- end
413
-
414
454
  # Get the source for an open file or create a new source if the file
415
455
  # exists on disk. Sources created from disk are not added to the open
416
456
  # workspace files, i.e., the version on disk remains the authoritative
@@ -424,5 +464,35 @@ module Solargraph
424
464
  raise FileNotFoundError, "File not found: #{filename}" unless workspace.has_file?(filename)
425
465
  workspace.source(filename)
426
466
  end
467
+
468
+ def handle_file_not_found filename, error
469
+ if workspace.source(filename)
470
+ Solargraph.logger.debug "#{filename} is not cataloged in the ApiMap"
471
+ nil
472
+ else
473
+ raise error
474
+ end
475
+ end
476
+
477
+ def maybe_map source
478
+ if source_map_hash.key?(source.filename)
479
+ return if source_map_hash[source.filename].code == source.code &&
480
+ source_map_hash[source.filename].source.synchronized? &&
481
+ source.synchronized?
482
+ if source.synchronized?
483
+ new_map = Solargraph::SourceMap.map(source)
484
+ unless source_map_hash[source.filename].try_merge!(new_map)
485
+ source_map_hash[source.filename] = new_map
486
+ @synchronized = false
487
+ end
488
+ else
489
+ # @todo Smelly instance variable access
490
+ source_map_hash[source.filename].instance_variable_set(:@source, source)
491
+ end
492
+ else
493
+ source_map_hash[source.filename] = Solargraph::SourceMap.map(source)
494
+ @synchronized = false
495
+ end
496
+ end
427
497
  end
428
498
  end