solargraph 0.39.14 → 0.40.1

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -8
  3. data/CHANGELOG.md +988 -0
  4. data/Rakefile +12 -1
  5. data/SPONSORS.md +1 -0
  6. data/lib/solargraph.rb +2 -4
  7. data/lib/solargraph/api_map.rb +75 -74
  8. data/lib/solargraph/api_map/cache.rb +2 -2
  9. data/lib/solargraph/api_map/store.rb +4 -8
  10. data/lib/solargraph/{bundle.rb → bench.rb} +6 -2
  11. data/lib/solargraph/compat.rb +14 -0
  12. data/lib/solargraph/complex_type.rb +2 -2
  13. data/lib/solargraph/convention.rb +13 -4
  14. data/lib/solargraph/convention/base.rb +16 -8
  15. data/lib/solargraph/convention/gemfile.rb +2 -5
  16. data/lib/solargraph/convention/gemspec.rb +3 -6
  17. data/lib/solargraph/convention/rspec.rb +3 -6
  18. data/lib/solargraph/documentor.rb +2 -0
  19. data/lib/solargraph/environ.rb +11 -6
  20. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +6 -1
  21. data/lib/solargraph/language_server/message/text_document/definition.rb +1 -1
  22. data/lib/solargraph/language_server/message/text_document/formatting.rb +17 -19
  23. data/lib/solargraph/library.rb +8 -10
  24. data/lib/solargraph/parser/legacy/node_chainer.rb +7 -7
  25. data/lib/solargraph/parser/legacy/node_methods.rb +5 -0
  26. data/lib/solargraph/parser/legacy/node_processors/ivasgn_node.rb +1 -1
  27. data/lib/solargraph/parser/legacy/node_processors/send_node.rb +36 -23
  28. data/lib/solargraph/parser/node_processor/base.rb +3 -0
  29. data/lib/solargraph/parser/rubyvm/node_chainer.rb +9 -9
  30. data/lib/solargraph/parser/rubyvm/node_methods.rb +11 -1
  31. data/lib/solargraph/parser/rubyvm/node_processors/args_node.rb +1 -1
  32. data/lib/solargraph/parser/rubyvm/node_processors/ivasgn_node.rb +1 -1
  33. data/lib/solargraph/parser/rubyvm/node_processors/send_node.rb +40 -29
  34. data/lib/solargraph/pin.rb +0 -3
  35. data/lib/solargraph/pin/common.rb +1 -1
  36. data/lib/solargraph/pin/conversions.rb +3 -4
  37. data/lib/solargraph/pin/documenting.rb +3 -9
  38. data/lib/solargraph/pin/method.rb +141 -7
  39. data/lib/solargraph/pin/method_alias.rb +1 -1
  40. data/lib/solargraph/position.rb +2 -14
  41. data/lib/solargraph/shell.rb +1 -1
  42. data/lib/solargraph/source.rb +10 -6
  43. data/lib/solargraph/source/chain.rb +18 -5
  44. data/lib/solargraph/source_map.rb +4 -1
  45. data/lib/solargraph/source_map/clip.rb +3 -2
  46. data/lib/solargraph/source_map/mapper.rb +10 -6
  47. data/lib/solargraph/type_checker.rb +35 -39
  48. data/lib/solargraph/type_checker/param_def.rb +1 -1
  49. data/lib/solargraph/version.rb +1 -1
  50. data/lib/solargraph/yard_map.rb +40 -47
  51. data/lib/solargraph/yard_map/core_fills.rb +185 -0
  52. data/lib/solargraph/yard_map/helpers.rb +16 -0
  53. data/lib/solargraph/yard_map/mapper.rb +14 -8
  54. data/lib/solargraph/{pin/yard_pin/constant.rb → yard_map/mapper/to_constant.rb} +6 -6
  55. data/lib/solargraph/yard_map/mapper/to_method.rb +78 -0
  56. data/lib/solargraph/{pin/yard_pin/namespace.rb → yard_map/mapper/to_namespace.rb} +6 -6
  57. data/lib/solargraph/yard_map/rdoc_to_yard.rb +1 -1
  58. data/lib/solargraph/yard_map/stdlib_fills.rb +43 -0
  59. data/lib/solargraph/yard_map/to_method.rb +79 -0
  60. data/solargraph.gemspec +4 -4
  61. metadata +20 -34
  62. data/lib/solargraph/core_fills.rb +0 -160
  63. data/lib/solargraph/pin/attribute.rb +0 -49
  64. data/lib/solargraph/pin/base_method.rb +0 -141
  65. data/lib/solargraph/pin/yard_pin.rb +0 -12
  66. data/lib/solargraph/pin/yard_pin/method.rb +0 -80
  67. data/lib/solargraph/pin/yard_pin/yard_mixin.rb +0 -20
  68. data/lib/solargraph/stdlib_fills.rb +0 -40
  69. data/travis-bundler.rb +0 -11
@@ -4,18 +4,22 @@ module Solargraph
4
4
  # An aggregation of a workspace and additional sources to be cataloged in an
5
5
  # ApiMap.
6
6
  #
7
- class Bundle
7
+ class Bench
8
8
  # @return [Workspace]
9
9
  attr_reader :workspace
10
10
 
11
11
  # @return [Array<Source>]
12
12
  attr_reader :opened
13
13
 
14
+ # @return [Array<Pin::Base>]
15
+ attr_reader :pins
16
+
14
17
  # @param workspace [Workspace]
15
18
  # @param opened [Array<Source>]
16
- def initialize workspace: Workspace.new, opened: []
19
+ def initialize workspace: Workspace.new, opened: [], pins: []
17
20
  @workspace = workspace
18
21
  @opened = opened
22
+ @pins = pins
19
23
  end
20
24
 
21
25
  # @return [Array<Source>]
@@ -7,3 +7,17 @@ unless Hash.method_defined?(:transform_values)
7
7
  end
8
8
  end
9
9
  end
10
+
11
+ unless Array.method_defined?(:sum)
12
+ class Array
13
+ def sum &block
14
+ inject(0) do |s, x|
15
+ if block
16
+ s + block.call(x)
17
+ else
18
+ s + x
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -12,7 +12,7 @@ module Solargraph
12
12
 
13
13
  # @param types [Array<UniqueType>]
14
14
  def initialize types = [UniqueType::UNDEFINED]
15
- @items = types
15
+ @items = types.uniq(&:to_s)
16
16
  end
17
17
 
18
18
  # @param api_map [ApiMap]
@@ -113,7 +113,7 @@ module Solargraph
113
113
  #
114
114
  # @param *strings [Array<String>] The type definitions to parse
115
115
  # @param partial [Boolean] True if the string is part of a another type
116
- # @return [ComplexType]
116
+ # @return [ComplexType, Array, nil]
117
117
  def parse *strings, partial: false
118
118
  @cache ||= {}
119
119
  unless partial
@@ -20,13 +20,22 @@ module Solargraph
20
20
  @@conventions.add convention.new
21
21
  end
22
22
 
23
- # @param source [Source]
23
+ # @param source_map [SourceMap]
24
24
  # @return [Environ]
25
- def self.for(source)
25
+ def self.for_local(source_map)
26
26
  result = Environ.new
27
- return result if source.filename.nil? || source.filename.empty?
28
27
  @@conventions.each do |conv|
29
- result.merge conv.environ if conv.match?(source)
28
+ result.merge conv.local(source_map)
29
+ end
30
+ result
31
+ end
32
+
33
+ # @param yard_map [YardMap]
34
+ # @return [Environ]
35
+ def self.for_global(yard_map)
36
+ result = Environ.new
37
+ @@conventions.each do |conv|
38
+ result.merge conv.global(yard_map)
30
39
  end
31
40
  result
32
41
  end
@@ -2,22 +2,30 @@
2
2
 
3
3
  module Solargraph
4
4
  module Convention
5
+ # The base class for Conventions.
6
+ #
7
+ # A Convention provides Environs that customize ApiMaps with additional
8
+ # pins and other information. Subclasses should implement the `local` and
9
+ # `global` methods as necessary.
10
+ #
5
11
  class Base
6
12
  EMPTY_ENVIRON = Environ.new
7
13
 
8
- # True if the source qualifies for this convention.
9
- # Subclasses should override this method.
14
+ # The Environ for a source map.
15
+ # Subclasses can override this method.
10
16
  #
11
- # @param source [Source]
12
- def match? source
13
- false
17
+ # @param source_map [SourceMap]
18
+ # @return [Environ]
19
+ def local source_map
20
+ EMPTY_ENVIRON
14
21
  end
15
22
 
16
- # The Environ for this convention.
17
- # Subclasses should override this method.
23
+ # The Environ for a YARD map.
24
+ # Subclasses can override this method.
18
25
  #
26
+ # @param yard_map [YardMap]
19
27
  # @return [Environ]
20
- def environ
28
+ def global yard_map
21
29
  EMPTY_ENVIRON
22
30
  end
23
31
  end
@@ -3,11 +3,8 @@
3
3
  module Solargraph
4
4
  module Convention
5
5
  class Gemfile < Base
6
- def match? source
7
- File.basename(source.filename) == 'Gemfile'
8
- end
9
-
10
- def environ
6
+ def local source_map
7
+ return EMPTY_ENVIRON unless File.basename(source_map.filename) == 'Gemfile'
11
8
  @environ ||= Environ.new(
12
9
  requires: ['bundler'],
13
10
  domains: ['Bundler::Dsl']
@@ -3,14 +3,11 @@
3
3
  module Solargraph
4
4
  module Convention
5
5
  class Gemspec < Base
6
- def match? source
7
- File.basename(source.filename).end_with?('.gemspec')
8
- end
9
-
10
- def environ
6
+ def local source_map
7
+ return EMPTY_ENVIRON unless File.basename(source_map.filename).end_with?('.gemspec')
11
8
  @environ ||= Environ.new(
12
9
  requires: ['rubygems'],
13
- overrides: [
10
+ pins: [
14
11
  Solargraph::Pin::Reference::Override.from_comment(
15
12
  'Gem::Specification.new',
16
13
  %(
@@ -3,18 +3,15 @@
3
3
  module Solargraph
4
4
  module Convention
5
5
  class Rspec < Base
6
- def match? source
7
- File.basename(source.filename) =~ /_spec\.rb$/
8
- end
9
-
10
- def environ
6
+ def local source_map
7
+ return EMPTY_ENVIRON unless File.basename(source_map.filename) =~ /_spec\.rb$/
11
8
  @environ ||= Environ.new(
12
9
  requires: ['rspec'],
13
10
  domains: ['RSpec::Matchers', 'RSpec::ExpectationGroups'],
14
11
  # This override is necessary due to an erroneous @return tag in
15
12
  # rspec's YARD documentation.
16
13
  # @todo The return types have been fixed (https://github.com/rspec/rspec-expectations/pull/1121)
17
- overrides: [
14
+ pins: [
18
15
  Solargraph::Pin::Reference::Override.method_return('RSpec::Matchers#expect', 'RSpec::Expectations::ExpectationTarget')
19
16
  ]
20
17
  )
@@ -55,6 +55,8 @@ module Solargraph
55
55
  false
56
56
  end
57
57
 
58
+ # @param directory [String]
59
+ # @return [Hash]
58
60
  def self.specs_from_bundle directory
59
61
  Solargraph.with_clean_env do
60
62
  Dir.chdir directory do
@@ -1,6 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Solargraph
4
+ # A collection of additional data, such as map pins and required paths, that
5
+ # can be added to an ApiMap.
6
+ #
7
+ # Conventions are used to add Environs.
8
+ #
4
9
  class Environ
5
10
  # @return [Array<String>]
6
11
  attr_reader :requires
@@ -9,22 +14,22 @@ module Solargraph
9
14
  attr_reader :domains
10
15
 
11
16
  # @return [Array<Pin::Reference::Override>]
12
- attr_reader :overrides
17
+ attr_reader :pins
13
18
 
14
19
  # @param requires [Array<String>]
15
20
  # @param domains [Array<String>]
16
- # @param overrides [Array<Pin::Reference::Override>]
17
- def initialize requires: [], domains: [], overrides: []
21
+ # @param pins [Array<Pin::Base>]
22
+ def initialize requires: [], domains: [], pins: []
18
23
  @requires = requires
19
24
  @domains = domains
20
- @overrides = overrides
25
+ @pins = pins
21
26
  end
22
27
 
23
28
  # @return [self]
24
29
  def clear
25
30
  domains.clear
26
31
  requires.clear
27
- overrides.clear
32
+ pins.clear
28
33
  self
29
34
  end
30
35
 
@@ -33,7 +38,7 @@ module Solargraph
33
38
  def merge other
34
39
  domains.concat other.domains
35
40
  requires.concat other.requires
36
- overrides.concat other.overrides
41
+ pins.concat other.pins
37
42
  self
38
43
  end
39
44
  end
@@ -34,7 +34,12 @@ module Solargraph
34
34
  LanguageServer::MessageTypes::INFO,
35
35
  ['Update now'] do |result|
36
36
  next unless result == 'Update now'
37
- o, s = Open3.capture2("gem update solargraph")
37
+ cmd = if host.options['useBundler']
38
+ 'bundle update solargraph'
39
+ else
40
+ 'gem update solargraph'
41
+ end
42
+ o, s = Open3.capture2(cmd)
38
43
  if s == 0
39
44
  host.show_message 'Successfully updated the Solargraph gem.', LanguageServer::MessageTypes::INFO
40
45
  host.send_notification '$/solargraph/restart', {}
@@ -13,7 +13,7 @@ module Solargraph::LanguageServer::Message::TextDocument
13
13
  def code_location
14
14
  suggestions = host.definitions_at(params['textDocument']['uri'], @line, @column)
15
15
  return nil if suggestions.empty?
16
- suggestions.reject{|pin| pin.location.nil?}.map do |pin|
16
+ suggestions.reject { |pin| pin.location.nil? || pin.location.filename.nil? }.map do |pin|
17
17
  {
18
18
  uri: file_to_uri(pin.location.filename),
19
19
  range: pin.location.range.to_hash
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'rubocop'
4
4
  require 'securerandom'
5
+ require 'tmpdir'
5
6
 
6
7
  module Solargraph
7
8
  module LanguageServer
@@ -12,25 +13,22 @@ module Solargraph
12
13
 
13
14
  def process
14
15
  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
16
+ Dir.mktmpdir do |tempdir|
17
+ tempfile = File.join(tempdir, File.basename(filename))
18
+ rubocop_file = Diagnostics::RubocopHelpers.find_rubocop_file(filename)
19
+ original = host.read_text(params['textDocument']['uri'])
20
+ File.write tempfile, original
21
+ begin
22
+ args = ['-a', '-f', 'fi', tempfile]
23
+ args.unshift('-c', fix_drive_letter(rubocop_file)) unless rubocop_file.nil?
24
+ options, paths = RuboCop::Options.new.parse(args)
25
+ store = RuboCop::ConfigStore.new
26
+ redirect_stdout { RuboCop::Runner.new(options, store).run(paths) }
27
+ result = File.read(tempfile)
28
+ format original, result
29
+ rescue RuboCop::ValidationError, RuboCop::ConfigNotFoundError => e
30
+ set_error(Solargraph::LanguageServer::ErrorCodes::INTERNAL_ERROR, "[#{e.class}] #{e.message}")
31
+ end
34
32
  end
35
33
  end
36
34
 
@@ -20,7 +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 bundle
23
+ api_map.catalog bench
24
24
  @synchronized = true
25
25
  @catalog_mutex = Mutex.new
26
26
  end
@@ -174,10 +174,10 @@ module Solargraph
174
174
  if cursor.comment?
175
175
  source = read(filename)
176
176
  offset = Solargraph::Position.to_offset(source.code, Solargraph::Position.new(line, column))
177
- lft = source.code[0..offset-1].match(/[\[<, ]([a-z0-9_:]*)\z/i)
177
+ lft = source.code[0..offset-1].match(/\[[a-z0-9_:<, ]*?([a-z0-9_:]*)\z/i)
178
178
  rgt = source.code[offset..-1].match(/^([a-z0-9_]*)(:[a-z0-9_:]*)?[\]>, ]/i)
179
179
  if lft && rgt
180
- tag = lft[1] + rgt[1]
180
+ tag = (lft[1] + rgt[1]).sub(/:+$/, '')
181
181
  clip = api_map.clip(cursor)
182
182
  clip.translate tag
183
183
  else
@@ -209,7 +209,6 @@ module Solargraph
209
209
  # @return [Array<Solargraph::Range>]
210
210
  # @todo Take a Location instead of filename/line/column
211
211
  def references_from filename, line, column, strip: false
212
- # checkout filename
213
212
  cursor = api_map.cursor_at(filename, Position.new(line, column))
214
213
  clip = api_map.clip(cursor)
215
214
  pins = clip.define
@@ -222,7 +221,7 @@ module Solargraph
222
221
  referenced = definitions_at(loc.filename, loc.range.ending.line, loc.range.ending.character)
223
222
  # HACK: The additional location comparison is necessary because
224
223
  # Clip#define can return proxies for parameter pins
225
- referenced.any?{|r| r == pin || r.location == pin.location}
224
+ referenced.any? { |r| r == pin || r.location == pin.location }
226
225
  end
227
226
  # HACK: for language clients that exclude special characters from the start of variable names
228
227
  if strip && match = cursor.word.match(/^[^a-z0-9_]+/i)
@@ -290,7 +289,6 @@ module Solargraph
290
289
  # @param filename [String]
291
290
  # @return [Array<Solargraph::Pin::Base>]
292
291
  def document_symbols filename
293
- # checkout filename
294
292
  api_map.document_symbols(filename)
295
293
  end
296
294
 
@@ -351,7 +349,7 @@ module Solargraph
351
349
  @catalog_mutex.synchronize do
352
350
  break if synchronized?
353
351
  logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}"
354
- api_map.catalog bundle
352
+ api_map.catalog bench
355
353
  @synchronized = true
356
354
  logger.info "Catalog complete (#{api_map.source_maps.length} files, #{api_map.pins.length} pins)" if logger.info?
357
355
  end
@@ -403,9 +401,9 @@ module Solargraph
403
401
  @api_map ||= Solargraph::ApiMap.new
404
402
  end
405
403
 
406
- # @return [Bundle]
407
- def bundle
408
- Bundle.new(
404
+ # @return [Bench]
405
+ def bench
406
+ Bench.new(
409
407
  workspace: workspace,
410
408
  opened: @current ? [@current] : []
411
409
  )
@@ -14,7 +14,7 @@ module Solargraph
14
14
  def initialize node, filename = nil, in_block = false
15
15
  @node = node
16
16
  @filename = filename
17
- @in_block = in_block
17
+ @in_block = in_block ? 1 : 0
18
18
  end
19
19
 
20
20
  # @return [Source::Chain]
@@ -51,9 +51,9 @@ module Solargraph
51
51
  return generate_links(n.children[0]) if n.type == :splat
52
52
  result = []
53
53
  if n.type == :block
54
- @in_block = true
54
+ @in_block += 1
55
55
  result.concat generate_links(n.children[0])
56
- @in_block = false
56
+ @in_block -= 1
57
57
  elsif n.type == :send
58
58
  if n.children[0].is_a?(::Parser::AST::Node)
59
59
  result.concat generate_links(n.children[0])
@@ -61,23 +61,23 @@ module Solargraph
61
61
  n.children[2..-1].each do |c|
62
62
  args.push NodeChainer.chain(c)
63
63
  end
64
- result.push Chain::Call.new(n.children[1].to_s, args, @in_block || block_passed?(n))
64
+ result.push Chain::Call.new(n.children[1].to_s, args, @in_block > 0 || block_passed?(n))
65
65
  elsif n.children[0].nil?
66
66
  args = []
67
67
  n.children[2..-1].each do |c|
68
68
  args.push NodeChainer.chain(c)
69
69
  end
70
- result.push Chain::Call.new(n.children[1].to_s, args, @in_block || block_passed?(n))
70
+ result.push Chain::Call.new(n.children[1].to_s, args, @in_block > 0 || block_passed?(n))
71
71
  else
72
72
  raise "No idea what to do with #{n}"
73
73
  end
74
74
  elsif n.type == :self
75
75
  result.push Chain::Head.new('self')
76
76
  elsif n.type == :zsuper
77
- result.push Chain::ZSuper.new('super', @in_block || block_passed?(n))
77
+ result.push Chain::ZSuper.new('super', @in_block > 0 || block_passed?(n))
78
78
  elsif n.type == :super
79
79
  args = n.children.map { |c| NodeChainer.chain(c) }
80
- result.push Chain::Call.new('super', args, @in_block || block_passed?(n))
80
+ result.push Chain::Call.new('super', args, @in_block > 0 || block_passed?(n))
81
81
  elsif n.type == :const
82
82
  const = unpack_name(n)
83
83
  result.push Chain::Constant.new(const)
@@ -98,6 +98,7 @@ module Solargraph
98
98
 
99
99
  def convert_hash node
100
100
  return {} unless Parser.is_ast_node?(node) && node.type == :hash
101
+ return convert_hash(node.children[0].children[0]) if splatted_hash?(node)
101
102
  result = {}
102
103
  node.children.each do |pair|
103
104
  result[pair.children[0].children[0]] = Solargraph::Parser.chain(pair.children[1])
@@ -118,6 +119,10 @@ module Solargraph
118
119
  result
119
120
  end
120
121
 
122
+ def splatted_hash? node
123
+ Parser.is_ast_node?(node.children[0]) && node.children[0].type == :kwsplat
124
+ end
125
+
121
126
  # @todo Temporarily here for testing. Move to Solargraph::Parser.
122
127
  def call_nodes_from node
123
128
  return [] unless node.is_a?(::Parser::AST::Node)