solargraph 0.41.0 → 0.42.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eed08215481576c83b9ff5014648c0fa5fe7e458eb59eabcf86632496e4a9f32
4
- data.tar.gz: 8f036f65304ecf1401504a84a49bc34ed6d9be97308be8a1e080be884af100fc
3
+ metadata.gz: d4cd4acd35a7ce8242976cdd2b23d583bbd01392e3d79a16e3b4e181443e4858
4
+ data.tar.gz: 4cb4f369d97681a43d24a8fc3de14a454721c4334426655e23fffafceeb242c7
5
5
  SHA512:
6
- metadata.gz: '024920c42f1f1e37f604b616481bca07b91ec52ed9fd315e6e12a6cc0ab7c0a88ef9ecd827866ab52d66d2f43b679a97c68d3877b88adc5092fc9c2dc2dca5a1'
7
- data.tar.gz: 02c6de30fdac85feedbee8e8b3cbd35ea846a9cbb3a5cb8c476053208a63ae5781a45382fcca43e760875f3f5f911675bdec3f8ba318b122ef6ea0c42f735a29
6
+ metadata.gz: 5918eb0b9797ed3bed1fe97a702761889b4fb5b8280de655820d8a4319eefd651f4958e420b32468aac2ab197f657b8cb1386029654dcbd9b3a30bbdd2ca521b
7
+ data.tar.gz: 649a461beb8b23b69ae40bb347389602e20b42d0a689752d805beb2c08000a863c24d9e45b39d00ee544d37802be0762d9933a6d4e27b2617249d1443ca582f4
data/.travis.yml CHANGED
@@ -8,15 +8,10 @@ rvm:
8
8
  - jruby-head
9
9
  matrix:
10
10
  include:
11
- - rvm: 2.4
12
- os: osx
13
- - rvm: 2.6
14
- os: osx
15
11
  - rvm: 2.7
16
12
  os: osx
17
13
  allow_failures:
18
14
  - rvm: jruby-head
19
- - rvm: 3.0
20
15
  before_install:
21
16
  - gem update --system
22
17
  - gem install bundler
data/CHANGELOG.md CHANGED
@@ -1,3 +1,27 @@
1
+ ## 0.42.2 - June 14, 2021
2
+ - Improve download-core command output
3
+ - Ignore missing requests to client responses
4
+ - Add automatically required gems to YardMap
5
+ - Use closures to identify local variables
6
+
7
+ ## 0.42.1 - June 11, 2021
8
+ - YardMap#change sets new directory (#445)
9
+
10
+ ## 0.42.0 - June 11, 2021
11
+ - Improve YardMap efficiency
12
+ - Bench includes Workspace for cataloging
13
+ - Initialize confirms static features from options (#436)
14
+ - Enable simple repairs without incremental sync (#416)
15
+ - Discard unrecognized client responses
16
+ - Notify on use of closest match for core (#390)
17
+
18
+ ## 0.41.2 - June 9, 2021
19
+ - Rescue InvalidOffset in async diagnosis
20
+ - Remove erroneous escaping from Hover
21
+
22
+ ## 0.41.1 - May 31, 2021
23
+ - ApiMap handles required bundles (#443)
24
+
1
25
  ## 0.41.0 - May 30, 2021
2
26
  - Chain constant at last double colon with more than two nested namespaces
3
27
  - Fill Integer#times return type (#440)
data/lib/.rubocop.yml CHANGED
@@ -1,5 +1,5 @@
1
- Layout/EndOfLine:
2
- Enabled: false
1
+ AllCops:
2
+ NewCops: enable
3
3
  Style/MethodDefParentheses:
4
4
  Enabled: false
5
5
  Layout/EmptyLineAfterGuardClause:
@@ -10,6 +10,8 @@ Lint/RaiseException:
10
10
  Enabled: true
11
11
  Lint/StructNewOverride:
12
12
  Enabled: true
13
+ Metrics/MethodLength:
14
+ Max: 25
13
15
  Style/ExponentialNotation:
14
16
  Enabled: true
15
17
  Style/HashEachMethods:
@@ -18,4 +20,3 @@ Style/HashTransformKeys:
18
20
  Enabled: true
19
21
  Style/HashTransformValues:
20
22
  Enabled: true
21
-
@@ -17,7 +17,6 @@ module Solargraph
17
17
  autoload :BundlerMethods, 'solargraph/api_map/bundler_methods'
18
18
 
19
19
  include SourceToYard
20
- include BundlerMethods
21
20
 
22
21
  # @return [Array<String>]
23
22
  attr_reader :unresolved_requires
@@ -59,30 +58,14 @@ module Solargraph
59
58
  implicit.clear
60
59
  @cache.clear
61
60
  @source_map_hash = bench.source_maps.map { |s| [s.filename, s] }.to_h
62
- pins = []
63
- @required = Set.new
64
- local_path_hash.clear
61
+ pins = bench.source_maps.map(&:pins).flatten
62
+ external_requires = bench.external_requires
65
63
  source_map_hash.each_value do |map|
66
- pins.concat map.pins
67
- @required.merge map.requires.map(&:name)
68
64
  implicit.merge map.environ
69
65
  end
70
- @required.merge implicit.requires
71
- external_requires = []
72
- @required.each do |req|
73
- result = bench.load_paths.find do |path|
74
- full = Pathname.new(path).join("#{req}.rb").to_s
75
- @source_map_hash.key?(full)
76
- end
77
- if result
78
- local_path_hash[req] = Pathname.new(result).join("#{req}.rb").to_s
79
- else
80
- external_requires.push req unless result
81
- end
82
- end
83
- br = @required.include?('bundler/require') ? bench.gemnames.to_h { |gs| [gs.name, gs] } : {}
84
- @required.merge br.keys
85
- yard_map.change(external_requires, br, bench.gemnames)
66
+ external_requires.merge implicit.requires
67
+ external_requires.merge bench.workspace.config.required
68
+ yard_map.change(external_requires, bench.workspace.directory, bench.workspace.source_gems)
86
69
  @store = Store.new(yard_map.pins + implicit.pins + pins)
87
70
  @unresolved_requires = yard_map.unresolved_requires
88
71
  @rebindable_method_names = nil
@@ -105,11 +88,6 @@ module Solargraph
105
88
  @implicit ||= Environ.new
106
89
  end
107
90
 
108
- # @return [Hash{String => String}]
109
- def local_path_hash
110
- @local_paths ||= {}
111
- end
112
-
113
91
  # @param filename [String]
114
92
  # @param position [Position, Array(Integer, Integer)]
115
93
  # @return [Source::Cursor]
@@ -456,27 +434,6 @@ module Solargraph
456
434
  source_map_hash.keys.include?(filename)
457
435
  end
458
436
 
459
- # True if the specified file is included in the workspace.
460
- #
461
- # @param filename [String]
462
- def workspaced? filename
463
- workspace_filenames.include?(filename)
464
- end
465
-
466
- # @param location [Location]
467
- # @return [Location]
468
- def require_reference_at location
469
- map = source_map(location.filename)
470
- pin = map.requires.select { |p| p.location.range.contain?(location.range.start) }.first
471
- return nil if pin.nil?
472
- if local_path_hash.key?(pin.name)
473
- return Location.new(local_path_hash[pin.name], Solargraph::Range.from_to(0, 0, 0, 0))
474
- end
475
- yard_map.require_reference(pin.name)
476
- rescue FileNotFoundError
477
- nil
478
- end
479
-
480
437
  # Check if a class is a superclass of another class.
481
438
  #
482
439
  # @param sup [String] The superclass
@@ -508,11 +465,6 @@ module Solargraph
508
465
 
509
466
  private
510
467
 
511
- # @return [Array<String>]
512
- def workspace_filenames
513
- @workspace_filenames ||= []
514
- end
515
-
516
468
  # A hash of source maps with filename keys.
517
469
  #
518
470
  # @return [Hash{String => SourceMap}]
@@ -9,7 +9,7 @@ module Solargraph
9
9
  # @param directory [String]
10
10
  # @return [Hash]
11
11
  def require_from_bundle directory
12
- @require_from_bundle ||= begin
12
+ begin
13
13
  Solargraph.logger.info "Loading gems for bundler/require"
14
14
  Documentor.specs_from_bundle(directory)
15
15
  rescue BundleNotFoundError => e
@@ -17,11 +17,6 @@ module Solargraph
17
17
  {}
18
18
  end
19
19
  end
20
-
21
- # @return [void]
22
- def reset_require_from_bundle
23
- @require_from_bundle = nil
24
- end
25
20
  end
26
21
  end
27
22
  end
@@ -3,25 +3,25 @@
3
3
  require 'set'
4
4
 
5
5
  module Solargraph
6
- # A container of source maps and gem specs to be cataloged in an ApiMap.
6
+ # A container of source maps and workspace data to be cataloged in an ApiMap.
7
7
  #
8
8
  class Bench
9
9
  # @return [Set<SourceMap>]
10
10
  attr_reader :source_maps
11
11
 
12
- # @return [Set<String>]
13
- attr_reader :load_paths
12
+ # @return [Workspace]
13
+ attr_reader :workspace
14
14
 
15
15
  # @return [Set<String>]
16
- attr_reader :gemnames
16
+ attr_reader :external_requires
17
17
 
18
18
  # @param source_maps [Array<SourceMap>, Set<SourceMap>]
19
- # @param load_paths [Array<String>, Set<String>]
20
- # @param gemnames [Array<String>, Set<String>]
21
- def initialize source_maps: [], load_paths: [], gemnames: []
19
+ # @param workspace [Workspace]
20
+ # @param external_requires [Array<String>, Set<String>]
21
+ def initialize source_maps: [], workspace: Workspace.new, external_requires: []
22
22
  @source_maps = source_maps.to_set
23
- @load_paths = load_paths.to_set
24
- @gemnames = gemnames.to_set
23
+ @workspace = workspace
24
+ @external_requires = external_requires.to_set
25
25
  end
26
26
  end
27
27
  end
@@ -7,7 +7,7 @@ module Solargraph
7
7
  #
8
8
  class TypeCheck < Base
9
9
  def diagnose source, api_map
10
- return [] unless args.include?('always') || api_map.workspaced?(source.filename)
10
+ # return [] unless args.include?('always') || api_map.workspaced?(source.filename)
11
11
  severity = Diagnostics::Severities::ERROR
12
12
  level = (args.reverse.find { |a| ['normal', 'typed', 'strict', 'strong'].include?(a) }) || :normal
13
13
  checker = Solargraph::TypeChecker.new(source.filename, api_map: api_map, level: level.to_sym)
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'diff/lcs'
3
4
  require 'observer'
4
- require 'set'
5
5
  require 'securerandom'
6
+ require 'set'
6
7
 
7
8
  module Solargraph
8
9
  module LanguageServer
@@ -30,7 +31,7 @@ module Solargraph
30
31
  @cancel = []
31
32
  @buffer = String.new
32
33
  @stopped = true
33
- @next_request_id = 0
34
+ @next_request_id = 1
34
35
  @dynamic_capabilities = Set.new
35
36
  @registered_capabilities = Set.new
36
37
  end
@@ -107,9 +108,13 @@ module Solargraph
107
108
  end
108
109
  message
109
110
  elsif request['id']
110
- # @todo What if the id is invalid?
111
- requests[request['id']].process(request['result'])
112
- requests.delete request['id']
111
+ if requests[request['id']]
112
+ requests[request['id']].process(request['result'])
113
+ requests.delete request['id']
114
+ else
115
+ logger.warn "Discarding client response to unrecognized message #{request['id']}"
116
+ nil
117
+ end
113
118
  else
114
119
  logger.warn "Invalid message received."
115
120
  logger.debug request
@@ -165,8 +170,6 @@ module Solargraph
165
170
  # @return [void]
166
171
  def open_from_disk uri
167
172
  sources.open_from_disk(uri)
168
- library = library_for(uri)
169
- # library.open_from_disk uri_to_file(uri)
170
173
  diagnoser.schedule uri
171
174
  end
172
175
 
@@ -672,7 +675,8 @@ module Solargraph
672
675
  # @return [Source::Updater]
673
676
  def generate_updater params
674
677
  changes = []
675
- params['contentChanges'].each do |chng|
678
+ params['contentChanges'].each do |recvd|
679
+ chng = check_diff(params['textDocument']['uri'], recvd)
676
680
  changes.push Solargraph::Source::Change.new(
677
681
  (chng['range'].nil? ?
678
682
  nil :
@@ -688,6 +692,36 @@ module Solargraph
688
692
  )
689
693
  end
690
694
 
695
+ # @param uri [String]
696
+ # @param change [Hash]
697
+ # @return [Hash]
698
+ def check_diff uri, change
699
+ return change if change['range']
700
+ source = sources.find(uri)
701
+ return change if source.code.length + 1 != change['text'].length
702
+ diffs = Diff::LCS.diff(source.code, change['text'])
703
+ return change if diffs.length.zero? || diffs.length > 1 || diffs.first.length > 1
704
+ # @type [Diff::LCS::Change]
705
+ diff = diffs.first.first
706
+ return change unless diff.adding? && ['.', ':'].include?(diff.element)
707
+ position = Solargraph::Position.from_offset(source.code, diff.position)
708
+ {
709
+ 'range' => {
710
+ 'start' => {
711
+ 'line' => position.line,
712
+ 'character' => position.character
713
+ },
714
+ 'end' => {
715
+ 'line' => position.line,
716
+ 'character' => position.character
717
+ }
718
+ },
719
+ 'text' => diff.element
720
+ }
721
+ rescue Solargraph::FileNotFoundError
722
+ change
723
+ end
724
+
691
725
  # @return [Hash]
692
726
  def dynamic_capability_options
693
727
  @dynamic_capability_options ||= {
@@ -787,7 +821,7 @@ module Solargraph
787
821
  next unless uuid
788
822
  cur = ((library.source_map_hash.keys.length.to_f / total.to_f) * 100).to_i
789
823
  if cur > pct && cur % mod == 0
790
- pct = cur
824
+ pct = cur
791
825
  send_notification '$/progress', {
792
826
  token: uuid,
793
827
  value: {
@@ -62,7 +62,15 @@ module Solargraph
62
62
  end
63
63
  current = mutex.synchronize { queue.shift }
64
64
  return if queue.include?(current)
65
- host.diagnose current
65
+ begin
66
+ host.diagnose current
67
+ rescue InvalidOffsetError
68
+ # @todo This error can occur when the Source is out of sync with
69
+ # with the ApiMap. It's probably not the best way to handle it,
70
+ # but it's quick and easy.
71
+ Logging.logger.warn "Deferring diagnosis due to invalid offset: #{current}"
72
+ mutex.synchronize { queue.push current }
73
+ end
66
74
  end
67
75
 
68
76
  private
@@ -76,7 +76,7 @@ module Solargraph
76
76
  source = Solargraph::Source.load(UriHelpers.uri_to_file(uri))
77
77
  open_source_hash[uri] = source
78
78
  end
79
-
79
+
80
80
  # Update an existing source.
81
81
  #
82
82
  # @raise [FileNotFoundError] if the URI does not match an open source.
@@ -51,6 +51,7 @@ module Solargraph
51
51
  end
52
52
 
53
53
  def static_completion
54
+ return {} unless host.options['completion']
54
55
  {
55
56
  completionProvider: {
56
57
  resolveProvider: true,
@@ -84,18 +85,21 @@ module Solargraph
84
85
  end
85
86
 
86
87
  def static_hover
88
+ return {} unless host.options['hover']
87
89
  {
88
90
  hoverProvider: true
89
91
  }
90
92
  end
91
93
 
92
94
  def static_document_formatting
95
+ return {} unless host.options['formatting']
93
96
  {
94
97
  documentFormattingProvider: true
95
98
  }
96
99
  end
97
100
 
98
101
  def static_document_symbols
102
+ return {} unless host.options['symbols']
99
103
  {
100
104
  documentSymbolProvider: true
101
105
  }
@@ -108,6 +112,7 @@ module Solargraph
108
112
  end
109
113
 
110
114
  def static_definitions
115
+ return {} unless host.options['definitions']
111
116
  {
112
117
  definitionProvider: true
113
118
  }
@@ -120,12 +125,14 @@ module Solargraph
120
125
  end
121
126
 
122
127
  def static_references
128
+ return {} unless host.options['references']
123
129
  {
124
130
  referencesProvider: true
125
131
  }
126
132
  end
127
133
 
128
134
  def static_folding_range
135
+ return {} unless host.options['folding']
129
136
  {
130
137
  foldingRangeProvider: true
131
138
  }
@@ -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}`" 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")
@@ -42,9 +42,7 @@ module Solargraph
42
42
  # @return [void]
43
43
  def process request
44
44
  message = @host.receive(request)
45
- message.send_response
46
- # tmp = @host.flush
47
- # write tmp unless tmp.empty?
45
+ message && message.send_response
48
46
  end
49
47
 
50
48
  def shutdown
@@ -46,8 +46,10 @@ module Solargraph
46
46
  # @return [void]
47
47
  def attach source
48
48
  mutex.synchronize do
49
- if @current && @current.filename != source.filename && source_map_hash.key?(@current.filename) && !workspace.has_file?(@current.filename)
49
+ if @current && (!source || @current.filename != source.filename) && source_map_hash.key?(@current.filename) && !workspace.has_file?(@current.filename)
50
50
  source_map_hash.delete @current.filename
51
+ source_map_external_require_hash.delete @current.filename
52
+ @external_requires = nil
51
53
  @synchronized = false
52
54
  end
53
55
  @current = source
@@ -252,7 +254,18 @@ module Solargraph
252
254
  end
253
255
 
254
256
  def locate_ref location
255
- api_map.require_reference_at location
257
+ map = source_map_hash[location.filename]
258
+ return if map.nil?
259
+ pin = map.requires.select { |p| p.location.range.contain?(location.range.start) }.first
260
+ return nil if pin.nil?
261
+ workspace.require_paths.each do |path|
262
+ full = Pathname.new(path).join("#{pin.name}.rb").to_s
263
+ next unless source_map_hash.key?(full)
264
+ return Location.new(full, Solargraph::Range.from_to(0, 0, 0, 0))
265
+ end
266
+ api_map.yard_map.require_reference(pin.name)
267
+ rescue FileNotFoundError
268
+ nil
256
269
  end
257
270
 
258
271
  # Get an array of pins that match a path.
@@ -326,6 +339,7 @@ module Solargraph
326
339
  return [] unless open?(filename)
327
340
  result = []
328
341
  source = read(filename)
342
+ catalog
329
343
  repargs = {}
330
344
  workspace.config.reporters.each do |line|
331
345
  if line == 'all!'
@@ -361,12 +375,10 @@ module Solargraph
361
375
  end
362
376
 
363
377
  def bench
364
- source_maps = @current ? [@current] : []
365
- source_maps.concat source_map_hash.values
366
378
  Bench.new(
367
- source_maps: source_maps,
368
- load_paths: workspace.require_paths,
369
- gemnames: workspace.gemnames
379
+ source_maps: source_map_hash.values,
380
+ workspace: workspace,
381
+ external_requires: external_requires
370
382
  )
371
383
  end
372
384
 
@@ -422,6 +434,8 @@ module Solargraph
422
434
  if src
423
435
  Logging.logger.debug "Mapping #{src.filename}"
424
436
  source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
437
+ find_external_requires(source_map_hash[src.filename])
438
+ source_map_hash[src.filename]
425
439
  else
426
440
  false
427
441
  end
@@ -431,6 +445,7 @@ module Solargraph
431
445
  def map!
432
446
  workspace.sources.each do |src|
433
447
  source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
448
+ find_external_requires(source_map_hash[src.filename])
434
449
  end
435
450
  self
436
451
  end
@@ -439,8 +454,29 @@ module Solargraph
439
454
  @pins ||= []
440
455
  end
441
456
 
457
+ def external_requires
458
+ @external_requires ||= source_map_external_require_hash.values.flatten.to_set
459
+ end
460
+
442
461
  private
443
462
 
463
+ def source_map_external_require_hash
464
+ @source_map_external_require_hash ||= {}
465
+ end
466
+
467
+ # @param source_map [SourceMap]
468
+ def find_external_requires source_map
469
+ new_set = source_map.requires.map(&:name).to_set
470
+ # return if new_set == source_map_external_require_hash[source_map.filename]
471
+ source_map_external_require_hash[source_map.filename] = new_set.reject do |path|
472
+ workspace.require_paths.any? do |base|
473
+ full = Pathname.new(base).join("#{path}.rb").to_s
474
+ workspace.filenames.include?(full)
475
+ end
476
+ end
477
+ @external_requires = nil
478
+ end
479
+
444
480
  # @return [Mutex]
445
481
  def mutex
446
482
  @mutex ||= Mutex.new
@@ -475,6 +511,7 @@ module Solargraph
475
511
  end
476
512
 
477
513
  def maybe_map source
514
+ return unless source
478
515
  if source_map_hash.key?(source.filename)
479
516
  return if source_map_hash[source.filename].code == source.code &&
480
517
  source_map_hash[source.filename].source.synchronized? &&
@@ -483,6 +520,7 @@ module Solargraph
483
520
  new_map = Solargraph::SourceMap.map(source)
484
521
  unless source_map_hash[source.filename].try_merge!(new_map)
485
522
  source_map_hash[source.filename] = new_map
523
+ find_external_requires(source_map_hash[source.filename])
486
524
  @synchronized = false
487
525
  end
488
526
  else
@@ -491,6 +529,7 @@ module Solargraph
491
529
  end
492
530
  else
493
531
  source_map_hash[source.filename] = Solargraph::SourceMap.map(source)
532
+ find_external_requires(source_map_hash[source.filename])
494
533
  @synchronized = false
495
534
  end
496
535
  end
@@ -3,7 +3,8 @@
3
3
  module Solargraph
4
4
  module Pin
5
5
  class LocalVariable < BaseVariable
6
- include Localized
6
+ # @return [Range]
7
+ attr_reader :presence
7
8
 
8
9
  def initialize assignment: nil, presence: nil, **splat
9
10
  super(**splat)
@@ -16,6 +17,51 @@ module Solargraph
16
17
  @presence = pin.presence
17
18
  true
18
19
  end
20
+
21
+ # @param other [Pin::Base] The caller's block
22
+ # @param position [Position, Array(Integer, Integer)] The caller's position
23
+ # @return [Boolean]
24
+ def visible_from?(other, position)
25
+ position = Position.normalize(position)
26
+ other.filename == filename &&
27
+ match_tags(other.full_context.tag, full_context.tag) &&
28
+ (other == closure ||
29
+ (closure.location.range.contain?(other.location.range.start) && closure.location.range.contain?(other.location.range.ending))
30
+ ) &&
31
+ presence.contain?(position)
32
+ end
33
+
34
+ # @param other_loc [Location]
35
+ def visible_at?(other_closure, other_loc)
36
+ return true if location.filename == other_loc.filename &&
37
+ presence.include?(other_loc.range.start) &&
38
+ match_named_closure(other_closure, closure)
39
+ end
40
+
41
+ private
42
+
43
+ # @param tag1 [String]
44
+ # @param tag2 [String]
45
+ # @return [Boolean]
46
+ def match_tags tag1, tag2
47
+ # @todo This is an unfortunate hack made necessary by a discrepancy in
48
+ # how tags indicate the root namespace. The long-term solution is to
49
+ # standardize it, whether it's `Class<>`, an empty string, or
50
+ # something else.
51
+ tag1 == tag2 ||
52
+ (['', 'Class<>'].include?(tag1) && ['', 'Class<>'].include?(tag2))
53
+ end
54
+
55
+ def match_named_closure needle, haystack
56
+ return true if needle == haystack
57
+ cursor = haystack
58
+ until cursor.nil?
59
+ return true if needle.path == cursor.path
60
+ return false if cursor.path && !cursor.path.empty?
61
+ cursor = cursor.closure
62
+ end
63
+ false
64
+ end
19
65
  end
20
66
  end
21
67
  end
@@ -74,10 +74,14 @@ module Solargraph
74
74
  desc 'download-core [VERSION]', 'Download core documentation'
75
75
  def download_core version = nil
76
76
  ver = version || Solargraph::YardMap::CoreDocs.best_download
77
+ if RUBY_VERSION != ver
78
+ puts "Documentation for #{RUBY_VERSION} is not available. Reverting to closest match..."
79
+ end
77
80
  puts "Downloading docs for #{ver}..."
78
81
  Solargraph::YardMap::CoreDocs.download ver
79
82
  # Clear cached documentation if it exists
80
83
  FileUtils.rm_rf Dir.glob(File.join(Solargraph::YardMap::CoreDocs.cache_dir, ver, '*.ser'))
84
+ puts "Download complete."
81
85
  rescue ArgumentError => e
82
86
  STDERR.puts "ERROR: #{e.message}"
83
87
  STDERR.puts "Run `solargraph available-cores` for a list."
@@ -77,6 +77,7 @@ module Solargraph
77
77
  end
78
78
  if match
79
79
  type = extra_return_type(ol, context)
80
+ break if type
80
81
  type = ComplexType.try_parse(*ol.tag(:return).types).self_to(context.to_s).qualify(api_map, context.namespace) if ol.has_tag?(:return) && ol.tag(:return).types && !ol.tag(:return).types.empty? && (type.nil? || type.undefined?)
81
82
  type ||= ComplexType::UNDEFINED
82
83
  end
@@ -170,8 +171,8 @@ module Solargraph
170
171
  # @param context [ComplexType]
171
172
  # @return [ComplexType]
172
173
  def extra_return_type docstring, context
173
- if docstring.has_tag?(:return_single_parameter) && context.subtypes.one?
174
- return context.subtypes.first
174
+ if docstring.has_tag?(:return_single_parameter) #&& context.subtypes.one?
175
+ return context.subtypes.first || ComplexType::UNDEFINED
175
176
  elsif docstring.has_tag?(:return_value_parameter) && context.value_types.one?
176
177
  return context.value_types.first
177
178
  end
@@ -131,7 +131,8 @@ module Solargraph
131
131
  # @return [Array<Pin::LocalVariable>]
132
132
  def locals_at(location)
133
133
  return [] if location.filename != filename
134
- locals.select { |pin| pin.visible_at?(location) }
134
+ closure = locate_named_path_pin(location.range.start.line, location.range.start.character)
135
+ locals.select { |pin| pin.visible_at?(closure, location) }
135
136
  end
136
137
 
137
138
  class << self
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Solargraph
4
- VERSION = '0.41.0'
4
+ VERSION = '0.42.2'
5
5
  end
@@ -18,6 +18,7 @@ module Solargraph
18
18
 
19
19
  # @return [Array<String>]
20
20
  attr_reader :gemnames
21
+ alias source_gems gemnames
21
22
 
22
23
  # @param directory [String]
23
24
  # @param config [Config, nil]
@@ -20,6 +20,8 @@ module Solargraph
20
20
  autoload :Helpers, 'solargraph/yard_map/helpers'
21
21
  autoload :ToMethod, 'solargraph/yard_map/to_method'
22
22
 
23
+ include ApiMap::BundlerMethods
24
+
23
25
  CoreDocs.require_minimum
24
26
 
25
27
  def stdlib_paths
@@ -37,33 +39,16 @@ module Solargraph
37
39
  end
38
40
  end
39
41
 
40
- # @return [Array<String>]
41
- attr_reader :required
42
-
43
42
  # @return [Boolean]
44
43
  attr_writer :with_dependencies
45
44
 
46
- # A hash of gem names and the version numbers to include in the map.
47
- #
48
- # @return [Hash{String => String}]
49
- attr_reader :gemset
50
-
51
- # @param required [Array<String>]
52
- # @param gemset [Hash{String => String}]
45
+ # @param required [Array<String>, Set<String>]
46
+ # @param directory [String]
47
+ # @param source_gems [Array<String>, Set<String>]
53
48
  # @param with_dependencies [Boolean]
54
- def initialize(required: [], gemset: {}, with_dependencies: true)
55
- # HACK: YardMap needs its own copy of this array
56
- @required = required.clone
57
- # HACK: Hardcoded YAML handling
58
- @required.push 'psych' if @required.include?('yaml')
49
+ def initialize(required: [], directory: '', source_gems: [], with_dependencies: true)
59
50
  @with_dependencies = with_dependencies
60
- @gem_paths = {}
61
- @stdlib_namespaces = []
62
- @gemset = gemset
63
- @source_gems = []
64
- process_requires
65
- yardocs.uniq!
66
- @pin_select_cache = {}
51
+ change required.to_set, directory, source_gems.to_set
67
52
  end
68
53
 
69
54
  # @return [Array<Solargraph::Pin::Base>]
@@ -76,25 +61,24 @@ module Solargraph
76
61
  @with_dependencies
77
62
  end
78
63
 
79
- # @param new_requires [Array<String>]
80
- # @param new_gemset [Hash{String => String}]
64
+ # @param new_requires [Set<String>] Required paths to use for loading gems
65
+ # @param new_directory [String] The workspace directory
66
+ # @param new_source_gems [Set<String>] Gems under local development (i.e., part of the workspace)
81
67
  # @return [Boolean]
82
- def change new_requires, new_gemset, source_gems = []
68
+ def change new_requires, new_directory, new_source_gems
69
+ return false if new_requires == base_required && new_directory == @directory && new_source_gems == @source_gems
70
+ @gem_paths = {}
71
+ base_required.replace new_requires
72
+ required.replace new_requires
83
73
  # HACK: Hardcoded YAML handling
84
- new_requires.push 'psych' if new_requires.include?('yaml')
85
- if new_requires.uniq.sort == required.uniq.sort && new_gemset == gemset && @source_gems.uniq.sort == source_gems.uniq.sort
86
- false
87
- else
88
- required.clear
89
- required.concat new_requires
90
- @gemset = new_gemset
91
- @source_gems = source_gems
92
- process_requires
93
- @rebindable_method_names = nil
94
- @pin_class_hash = nil
95
- @pin_select_cache = {}
96
- true
97
- end
74
+ required.add 'psych' if new_requires.include?('yaml')
75
+ @source_gems = new_source_gems
76
+ @directory = new_directory
77
+ process_requires
78
+ @rebindable_method_names = nil
79
+ @pin_class_hash = nil
80
+ @pin_select_cache = {}
81
+ true
98
82
  end
99
83
 
100
84
  # @return [Set<String>]
@@ -111,6 +95,11 @@ module Solargraph
111
95
  @yardocs ||= []
112
96
  end
113
97
 
98
+ # @return [Set<String>]
99
+ def required
100
+ @required ||= Set.new
101
+ end
102
+
114
103
  # @return [Array<String>]
115
104
  def unresolved_requires
116
105
  @unresolved_requires ||= []
@@ -163,6 +152,14 @@ module Solargraph
163
152
  @stdlib_pins ||= []
164
153
  end
165
154
 
155
+ def base_required
156
+ @base_required ||= Set.new
157
+ end
158
+
159
+ def directory
160
+ @directory ||= ''
161
+ end
162
+
166
163
  private
167
164
 
168
165
  # @return [YardMap::Cache]
@@ -193,6 +190,8 @@ module Solargraph
193
190
 
194
191
  # @return [void]
195
192
  def process_requires
193
+ @gemset = process_gemsets
194
+ required.merge @gemset.keys if required.include?('bundler/require')
196
195
  pins.replace core_pins
197
196
  unresolved_requires.clear
198
197
  stdlib_pins.clear
@@ -252,6 +251,11 @@ module Solargraph
252
251
  pins.concat environ.pins
253
252
  end
254
253
 
254
+ def process_gemsets
255
+ return {} if directory.empty? || !File.file?(File.join(directory, 'Gemfile'))
256
+ require_from_bundle(directory)
257
+ end
258
+
255
259
  # @param spec [Gem::Specification]
256
260
  # @return [void]
257
261
  def add_gem_dependencies spec
@@ -82,6 +82,18 @@ module Solargraph
82
82
  Override.method_return('Class#allocate', 'self'),
83
83
  Override.method_return('Class.allocate', 'Class<Object>'),
84
84
 
85
+ Override.from_comment('Enumerable#detect', %(
86
+ @overload detect(&block)
87
+ @return_single_parameter
88
+ @overload detect()
89
+ @return [Enumerator]
90
+ )),
91
+ Override.from_comment('Enumerable#find', %(
92
+ @overload find(&block)
93
+ @return_single_parameter
94
+ @overload find()
95
+ @return [Enumerator]
96
+ )),
85
97
  Override.method_return('Enumerable#select', 'self'),
86
98
 
87
99
  Override.method_return('File.absolute_path', 'String'),
@@ -111,7 +123,12 @@ module Solargraph
111
123
  @param y [Numeric]
112
124
  @return [Numeric]
113
125
  )),
114
- Override.method_return('Integer#times', 'Enumerator', delete: [:overload]),
126
+ Override.from_comment('Integer#times', %(
127
+ @overload times(&block)
128
+ @return [Integer]
129
+ @overload times()
130
+ @return [Enumerator]
131
+ )),
115
132
 
116
133
  Override.method_return('Kernel#puts', 'nil'),
117
134
 
data/solargraph.gemspec CHANGED
@@ -19,9 +19,10 @@ Gem::Specification.new do |s|
19
19
 
20
20
  s.required_ruby_version = '>= 2.4'
21
21
 
22
- s.add_runtime_dependency 'backport', '~> 1.1'
22
+ s.add_runtime_dependency 'backport', '~> 1.2'
23
23
  s.add_runtime_dependency 'benchmark'
24
24
  s.add_runtime_dependency 'bundler', '>= 1.17.2'
25
+ s.add_runtime_dependency 'diff-lcs', '~> 1.4'
25
26
  s.add_runtime_dependency 'e2mmap'
26
27
  s.add_runtime_dependency 'jaro_winkler', '~> 1.5'
27
28
  s.add_runtime_dependency 'kramdown', '~> 2.3'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solargraph
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.41.0
4
+ version: 0.42.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fred Snyder
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-30 00:00:00.000000000 Z
11
+ date: 2021-06-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: backport
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.1'
19
+ version: '1.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.1'
26
+ version: '1.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: benchmark
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: 1.17.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: diff-lcs
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.4'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.4'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: e2mmap
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -463,7 +477,6 @@ files:
463
477
  - lib/solargraph/pin/keyword.rb
464
478
  - lib/solargraph/pin/keyword_param.rb
465
479
  - lib/solargraph/pin/local_variable.rb
466
- - lib/solargraph/pin/localized.rb
467
480
  - lib/solargraph/pin/method.rb
468
481
  - lib/solargraph/pin/method_alias.rb
469
482
  - lib/solargraph/pin/namespace.rb
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Solargraph
4
- module Pin
5
- module Localized
6
- # @return [Range]
7
- attr_reader :presence
8
-
9
- # @param other [Pin::Base] The caller's block
10
- # @param position [Position, Array(Integer, Integer)] The caller's position
11
- # @return [Boolean]
12
- def visible_from?(other, position)
13
- position = Position.normalize(position)
14
- other.filename == filename &&
15
- match_tags(other.full_context.tag, full_context.tag) &&
16
- (other == closure ||
17
- (closure.location.range.contain?(other.location.range.start) && closure.location.range.contain?(other.location.range.ending))
18
- ) &&
19
- presence.contain?(position)
20
- end
21
-
22
- # @param other_loc [Location]
23
- def visible_at?(other_loc)
24
- return false if location.filename != other_loc.filename
25
- presence.include?(other_loc.range.start)
26
- end
27
-
28
- private
29
-
30
- # @param tag1 [String]
31
- # @param tag2 [String]
32
- # @return [Boolean]
33
- def match_tags tag1, tag2
34
- # @todo This is an unfortunate hack made necessary by a discrepancy in
35
- # how tags indicate the root namespace. The long-term solution is to
36
- # standardize it, whether it's `Class<>`, an empty string, or
37
- # something else.
38
- tag1 == tag2 ||
39
- (['', 'Class<>'].include?(tag1) && ['', 'Class<>'].include?(tag2))
40
- end
41
- end
42
- end
43
- end