solargraph 0.30.2 → 0.31.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/lib/solargraph.rb +7 -0
  3. data/lib/solargraph/api_map.rb +31 -38
  4. data/lib/solargraph/api_map/store.rb +7 -1
  5. data/lib/solargraph/diagnostics/require_not_found.rb +2 -1
  6. data/lib/solargraph/language_server/host.rb +34 -83
  7. data/lib/solargraph/language_server/host/cataloger.rb +17 -7
  8. data/lib/solargraph/language_server/host/diagnoser.rb +19 -10
  9. data/lib/solargraph/language_server/host/dispatch.rb +110 -0
  10. data/lib/solargraph/language_server/host/sources.rb +100 -1
  11. data/lib/solargraph/language_server/message/base.rb +15 -11
  12. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +1 -1
  13. data/lib/solargraph/language_server/message/initialize.rb +32 -27
  14. data/lib/solargraph/language_server/message/text_document/completion.rb +1 -8
  15. data/lib/solargraph/language_server/transport/adapter.rb +26 -15
  16. data/lib/solargraph/language_server/transport/data_reader.rb +2 -2
  17. data/lib/solargraph/library.rb +30 -58
  18. data/lib/solargraph/live_map.rb +1 -1
  19. data/lib/solargraph/pin.rb +1 -0
  20. data/lib/solargraph/pin/base.rb +1 -1
  21. data/lib/solargraph/pin/base_method.rb +1 -1
  22. data/lib/solargraph/pin/method.rb +1 -1
  23. data/lib/solargraph/pin/method_alias.rb +15 -4
  24. data/lib/solargraph/plugin/process.rb +1 -1
  25. data/lib/solargraph/position.rb +1 -2
  26. data/lib/solargraph/server_methods.rb +1 -0
  27. data/lib/solargraph/shell.rb +0 -28
  28. data/lib/solargraph/source.rb +116 -20
  29. data/lib/solargraph/source/encoding_fixes.rb +1 -1
  30. data/lib/solargraph/source/source_chainer.rb +16 -8
  31. data/lib/solargraph/source_map.rb +11 -2
  32. data/lib/solargraph/source_map/clip.rb +1 -1
  33. data/lib/solargraph/source_map/mapper.rb +8 -5
  34. data/lib/solargraph/version.rb +1 -1
  35. data/lib/solargraph/views/environment.erb +3 -0
  36. data/lib/solargraph/workspace.rb +17 -14
  37. data/lib/solargraph/workspace/config.rb +1 -1
  38. data/lib/solargraph/yard_map.rb +6 -5
  39. data/lib/solargraph/yard_map/core_docs.rb +68 -18
  40. data/lib/solargraph/yard_map/core_gen.rb +47 -0
  41. data/lib/yard-coregen.rb +16 -0
  42. data/lib/yard-solargraph.rb +10 -1
  43. metadata +21 -4
@@ -12,7 +12,7 @@ module Solargraph
12
12
  string.dup.force_encoding('UTF-8')
13
13
  rescue ::Encoding::CompatibilityError, ::Encoding::UndefinedConversionError, ::Encoding::InvalidByteSequenceError => e
14
14
  # @todo Improve error handling
15
- STDERR.puts "Normalize error: #{e.message}"
15
+ Solargraph::Logging.logger.warn "Normalize error: #{e.message}"
16
16
  string
17
17
  end
18
18
  end
@@ -15,7 +15,7 @@ module Solargraph
15
15
  # @param position [Position]
16
16
  # @return [Source::Chain]
17
17
  def chain source, position
18
- raise "Not a source" unless source.is_a?(Source)
18
+ # raise "Not a source" unless source.is_a?(Source)
19
19
  new(source, position).chain
20
20
  end
21
21
  end
@@ -33,19 +33,23 @@ module Solargraph
33
33
  return Chain.new([Chain::Literal.new('Symbol')]) if phrase.start_with?(':') && !phrase.start_with?('::')
34
34
  begin
35
35
  return Chain.new([]) if phrase.end_with?('..')
36
- if !source.repaired? && source.parsed?
37
- node = source.node_at(position.line, position.column)
36
+ if source.synchronized?
37
+ if !source.repaired? && source.parsed?
38
+ node = source.node_at(position.line, position.column)
39
+ else
40
+ node = nil
41
+ node = source.node_at(fixed_position.line, fixed_position.column) unless source.error_ranges.any?{|r| r.nil? || r.include?(fixed_position)}
42
+ node = Source.parse(fixed_phrase) if node.nil?
43
+ end
38
44
  else
39
- node = nil
40
- node = source.node_at(fixed_position.line, fixed_position.column) unless source.error_ranges.any?{|r| r.nil? || r.include?(fixed_position)}
41
- node = Source.parse(fixed_phrase) if node.nil?
45
+ node = Source.parse(fixed_phrase)
42
46
  end
43
47
  rescue Parser::SyntaxError
44
48
  return Chain.new([Chain::UNDEFINED_CALL])
45
49
  end
46
50
  return Chain.new([Chain::UNDEFINED_CALL]) if node.nil? || (node.type == :sym && !phrase.start_with?(':'))
47
51
  chain = NodeChainer.chain(node, source.filename)
48
- if source.repaired? || !source.parsed?
52
+ if source.repaired? || !source.parsed? || !source.synchronized?
49
53
  if end_of_phrase.strip == '.'
50
54
  chain.links.push Chain::UNDEFINED_CALL
51
55
  elsif end_of_phrase.strip == '::'
@@ -158,7 +162,11 @@ module Solargraph
158
162
  signature = char + signature if char.match(/[a-z0-9:\._@\$\?\!]/i) and @source.code[index - 1] != '%'
159
163
  break if char == '$'
160
164
  if char == '@'
161
- signature = "@#{signature}" if @source.code[index-1, 1] == '@'
165
+ index -= 1
166
+ if @source.code[index, 1] == '@'
167
+ index -= 1
168
+ signature = "@#{signature}"
169
+ end
162
170
  break
163
171
  end
164
172
  elsif parens == 1 || brackets == 1 || squares == 1
@@ -1,3 +1,5 @@
1
+ require 'jaro_winkler'
2
+
1
3
  module Solargraph
2
4
  # An index of pins and other ApiMap-related data for a Source.
3
5
  #
@@ -66,7 +68,7 @@ module Solargraph
66
68
  # @param query [String]
67
69
  # @return [Array<Pin::Base>]
68
70
  def query_symbols query
69
- document_symbols.select{|pin| pin.path.include?(query)}
71
+ document_symbols.select{ |pin| fuzzy_string_match(pin.path, query) || fuzzy_string_match(pin.name, query) }
70
72
  end
71
73
 
72
74
  # @param position [Position]
@@ -85,7 +87,7 @@ module Solargraph
85
87
  # @return [Solargraph::Pin::Base]
86
88
  def locate_pin location
87
89
  # return nil unless location.start_with?("#{filename}:")
88
- pins.select{|pin| pin.location == location}.first
90
+ pins.select { |pin| pin.location == location }.first
89
91
  end
90
92
 
91
93
  def locate_named_path_pin line, character
@@ -156,5 +158,12 @@ module Solargraph
156
158
  # @todo Assuming the root pin is always valid
157
159
  found || pins.first
158
160
  end
161
+
162
+ # @param str1 [String]
163
+ # @param str2 [String]
164
+ # @return [Boolean]
165
+ def fuzzy_string_match str1, str2
166
+ JaroWinkler.distance(str1, str2) > 0.6
167
+ end
159
168
  end
160
169
  end
@@ -15,7 +15,7 @@ module Solargraph
15
15
  def define
16
16
  return [] if cursor.comment? || cursor.chain.literal?
17
17
  result = cursor.chain.define(api_map, context_pin, locals)
18
- result.concat(source_map.pins.select{ |p| p.location.range.start.line == cursor.position.line }) if result.empty?
18
+ result.concat((source_map.pins + source_map.locals).select{ |p| p.name == cursor.word && p.location.range.contain?(cursor.position) }) if result.empty?
19
19
  result
20
20
  end
21
21
 
@@ -12,6 +12,7 @@ module Solargraph
12
12
 
13
13
  # Generate the data.
14
14
  #
15
+ # @param source [Source]
15
16
  # @return [Array]
16
17
  def map source
17
18
  @source = source
@@ -57,8 +58,8 @@ module Solargraph
57
58
  end
58
59
 
59
60
  def process_comment position, comment
61
+ return unless comment =~ /(@\!method|@\!attribute|@\!domain|@\!macro|@\!parse)/
60
62
  cmnt = remove_inline_comment_hashes(comment)
61
- return unless cmnt =~ /(@\!method|@\!attribute|@\!domain|@\!macro|@\!parse)/
62
63
  parse = YARD::Docstring.parser.parse(cmnt)
63
64
  parse.directives.each { |d| process_directive(position, d) }
64
65
  end
@@ -72,7 +73,8 @@ module Solargraph
72
73
  when 'method'
73
74
  namespace = namespace_at(position)
74
75
  gen_src = Solargraph::SourceMap.load_string("def #{directive.tag.name};end")
75
- gen_pin = gen_src.pins.last # Method is last pin after root namespace
76
+ gen_pin = gen_src.pins.select{ |p| p.kind == Pin::METHOD }.first
77
+ return if gen_pin.nil?
76
78
  @pins.push Solargraph::Pin::Method.new(location, namespace.path, gen_pin.name, docstring.all, :instance, :public, gen_pin.parameters, nil)
77
79
  when 'attribute'
78
80
  namespace = namespace_at(position)
@@ -96,7 +98,7 @@ module Solargraph
96
98
  end
97
99
  when 'domain'
98
100
  namespace = namespace_at(position)
99
- namespace.domains.push directive.tag.text
101
+ namespace.domains.concat directive.tag.types unless directive.tag.types.nil?
100
102
  end
101
103
  end
102
104
 
@@ -107,10 +109,10 @@ module Solargraph
107
109
  comment.lines.each { |l|
108
110
  # Trim the comment and minimum leading whitespace
109
111
  p = l.gsub(/^#/, '')
110
- if num.nil? and !p.strip.empty?
112
+ if num.nil? && !p.strip.empty?
111
113
  num = p.index(/[^ ]/)
112
114
  started = true
113
- elsif started and !p.strip.empty?
115
+ elsif started && !p.strip.empty?
114
116
  cur = p.index(/[^ ]/)
115
117
  num = cur if cur < num
116
118
  end
@@ -120,6 +122,7 @@ module Solargraph
120
122
  end
121
123
 
122
124
  def process_comment_directives
125
+ return unless @code =~ /(@\!method|@\!attribute|@\!domain|@\!macro|@\!parse)/
123
126
  current = []
124
127
  last_line = nil
125
128
  @comments.each do |cmnt|
@@ -1,3 +1,3 @@
1
1
  module Solargraph
2
- VERSION = '0.30.2'
2
+ VERSION = '0.31.0'
3
3
  end
@@ -29,6 +29,9 @@
29
29
  <li>
30
30
  Core Documentation Version: <%= Solargraph::YardMap::CoreDocs.best_match %>
31
31
  </li>
32
+ <li>
33
+ Core Cache Directory: <%= Solargraph::YardMap::CoreDocs.cache_dir %>
34
+ </li>
32
35
  <li>
33
36
  Parser Target Version: <%= Solargraph::Source.parser.version %>
34
37
  </li>
@@ -10,6 +10,7 @@ module Solargraph
10
10
  attr_reader :directory
11
11
 
12
12
  # @param directory [String]
13
+ # @param config [Config, nil]
13
14
  def initialize directory = '', config = nil
14
15
  @directory = directory
15
16
  @config = config
@@ -28,7 +29,7 @@ module Solargraph
28
29
  # @param source [Solargraph::Source]
29
30
  # @return [Boolean] True if the source was added to the workspace
30
31
  def merge source
31
- unless directory == '*' || source_hash.has_key?(source.filename)
32
+ unless directory == '*' || source_hash.key?(source.filename)
32
33
  # Reload the config to determine if a new source should be included
33
34
  @config = Solargraph::Workspace::Config.new(directory)
34
35
  return false unless config.calculated.include?(source.filename)
@@ -53,7 +54,7 @@ module Solargraph
53
54
  # @param filename [String]
54
55
  # @return [Boolean] True if the source was removed from the workspace
55
56
  def remove filename
56
- return false unless source_hash.has_key?(filename)
57
+ return false unless source_hash.key?(filename)
57
58
  source_hash.delete filename
58
59
  true
59
60
  end
@@ -68,13 +69,15 @@ module Solargraph
68
69
  source_hash.values
69
70
  end
70
71
 
72
+ # @param filename [String]
71
73
  # @return [Boolean]
72
74
  def has_file? filename
73
- source_hash.has_key?(filename)
75
+ source_hash.key?(filename)
74
76
  end
75
77
 
76
78
  # Get a source by its filename.
77
79
  #
80
+ # @param filename [String]
78
81
  # @return [Solargraph::Source]
79
82
  def source filename
80
83
  source_hash[filename]
@@ -109,13 +112,13 @@ module Solargraph
109
112
  #
110
113
  # @return [Array<String>]
111
114
  def gemspecs
112
- return [] if directory.empty?
115
+ return [] if directory.empty? || directory == '*'
113
116
  @gemspecs ||= Dir[File.join(directory, '**/*.gemspec')]
114
117
  end
115
118
 
116
119
  # Synchronize the workspace from the provided updater.
117
120
  #
118
- # @param [Source::Updater]
121
+ # @param updater [Source::Updater]
119
122
  # @return [void]
120
123
  def synchronize! updater
121
124
  source_hash[updater.filename] = source_hash[updater.filename].synchronize(updater)
@@ -128,6 +131,7 @@ module Solargraph
128
131
  @source_hash ||= {}
129
132
  end
130
133
 
134
+ # @return [void]
131
135
  def load_sources
132
136
  source_hash.clear
133
137
  unless directory.empty? || directory == '*'
@@ -144,7 +148,7 @@ module Solargraph
144
148
  #
145
149
  # @return [Array<String>]
146
150
  def generate_require_paths
147
- return configured_require_paths if directory.empty? || !gemspec?
151
+ return configured_require_paths unless gemspec?
148
152
  result = []
149
153
  gemspecs.each do |file|
150
154
  base = File.dirname(file)
@@ -152,15 +156,14 @@ module Solargraph
152
156
  # workspace code, but this is how Gem::Specification.load does it
153
157
  # anyway.
154
158
  begin
155
- spec = eval(File.read(file), binding, file)
156
- next unless Gem::Specification === spec
157
- result.concat spec.require_paths.map{ |path| File.join(base, path) } unless spec.nil?
159
+ spec = eval(File.read(file), binding, file)
160
+ next unless Gem::Specification === spec
161
+ result.concat(spec.require_paths.map { |path| File.join(base, path) })
158
162
  rescue Exception => e
159
- # Don't die if we have an error during eval-ing a gem spec.
160
- # Concat the default lib directory instead.
161
- # @todo Should the client be informed of the error through
162
- # diagnostics or some other mechanism?
163
- result.push File.join(base, 'lib')
163
+ # Don't die if we have an error during eval-ing a gem spec.
164
+ # Concat the default lib directory instead.
165
+ Solargraph.logger.warn "Error reading #{file}: [#{e.class}] #{e.message}"
166
+ result.push File.join(base, 'lib')
164
167
  end
165
168
  end
166
169
  result.concat config.require_paths
@@ -15,7 +15,7 @@ module Solargraph
15
15
  # @return [Hash]
16
16
  attr_reader :raw_data
17
17
 
18
- # @param workspace [String]
18
+ # @param directory [String]
19
19
  def initialize directory = ''
20
20
  @directory = directory
21
21
  include_globs = ['**/*.rb']
@@ -7,9 +7,10 @@ module Solargraph
7
7
  class YardMap
8
8
  autoload :Cache, 'solargraph/yard_map/cache'
9
9
  autoload :CoreDocs, 'solargraph/yard_map/core_docs'
10
+ autoload :CoreGen, 'solargraph/yard_map/core_gen'
10
11
 
11
12
  CoreDocs.require_minimum
12
- @@stdlib_yardoc = CoreDocs.yard_stdlib_file
13
+ @@stdlib_yardoc = CoreDocs.yardoc_stdlib_file
13
14
  @@stdlib_paths = {}
14
15
  YARD::Registry.load! @@stdlib_yardoc
15
16
  YARD::Registry.all(:class, :module).each do |ns|
@@ -26,7 +27,7 @@ module Solargraph
26
27
  attr_writer :with_dependencies
27
28
 
28
29
  # @param required [Array<String>]
29
- # @param skip_dependencies [Boolean]
30
+ # @param with_dependencies [Boolean]
30
31
  def initialize(required: [], with_dependencies: true)
31
32
  # HACK: YardMap needs its own copy of this array
32
33
  @required = required.clone
@@ -79,7 +80,7 @@ module Solargraph
79
80
  YARD::Registry.load! y
80
81
  end
81
82
  rescue Exception => e
82
- STDERR.puts "Error loading yardoc '#{y}' #{e.class} #{e.message}"
83
+ Solargraph::Logging.logger.warn "Error loading yardoc '#{y}' #{e.class} #{e.message}"
83
84
  yardocs.delete y
84
85
  nil
85
86
  end
@@ -252,7 +253,7 @@ module Solargraph
252
253
  end
253
254
  rescue Gem::LoadError
254
255
  # This error probably indicates a bug in an installed gem
255
- STDERR.puts "Warning: failed to resolve #{dep.name} gem dependency for #{spec.name}"
256
+ Solargraph::Logging.logger.warn "Failed to resolve #{dep.name} gem dependency for #{spec.name}"
256
257
  end
257
258
  end
258
259
  result
@@ -266,7 +267,7 @@ module Solargraph
266
267
  .map{ |f| File.size(f) }
267
268
  .inject(:+)
268
269
  if !size.nil? && size > 20_000_000
269
- STDERR.puts "Warning: yardoc at #{y} is too large to process (#{size} bytes)"
270
+ Solargraph::Logging.logger.warn "Yardoc at #{y} is too large to process (#{size} bytes)"
270
271
  return []
271
272
  end
272
273
  result = []
@@ -8,25 +8,37 @@ module Solargraph
8
8
  # Tools for managing core documentation.
9
9
  #
10
10
  module CoreDocs
11
+ # The URL for downloading core documentation
11
12
  SOURCE = 'https://solargraph.org/download'
12
13
 
14
+ # The default core documentation version
15
+ DEFAULT = '2.2.2'
16
+
13
17
  class << self
18
+ # The directory where core documentation is installed.
19
+ #
20
+ # @return [String]
14
21
  def cache_dir
15
- @cache_dir ||= ENV["SOLARGRAPH_CACHE"] || File.join(Dir.home, '.solargraph', 'cache')
22
+ # The directory is not stored in a variable so it can be overridden
23
+ # in specs.
24
+ ENV['SOLARGRAPH_CACHE'] || File.join(Dir.home, '.solargraph', 'cache')
16
25
  end
17
26
 
18
27
  # Ensure installation of minimum documentation.
19
28
  #
29
+ # @return [void]
20
30
  def require_minimum
21
31
  return unless best_match.nil?
22
32
  FileUtils.mkdir_p cache_dir
23
- version_dir = File.join(cache_dir, '2.2.2')
24
- unless File.exist?(version_dir)
25
- FileUtils.cp File.join(Solargraph::YARDOC_PATH, '2.2.2.tar.gz'), cache_dir
26
- install_archive File.join(cache_dir, '2.2.2.tar.gz')
27
- end
33
+ FileUtils.cp File.join(Solargraph::YARDOC_PATH, "#{DEFAULT}.tar.gz"), cache_dir
34
+ install_archive File.join(cache_dir, "#{DEFAULT}.tar.gz")
28
35
  end
29
36
 
37
+ # True if core documentation is installed for the specified version
38
+ # number.
39
+ #
40
+ # @param ver [String] The version number to check
41
+ # @return [Boolean]
30
42
  def valid?(ver)
31
43
  dir = File.join(cache_dir, ver)
32
44
  return false unless File.directory?(dir)
@@ -35,6 +47,10 @@ module Solargraph
35
47
  true
36
48
  end
37
49
 
50
+ # Get a list of version numbers for currently installed core
51
+ # documentation.
52
+ #
53
+ # @return [Array<String>] The installed version numbers
38
54
  def versions
39
55
  dirs = Dir[File.join(cache_dir, '*')].map{|d| File.basename(d)}
40
56
  dirs.keep_if{|d| valid?(d)}
@@ -42,40 +58,67 @@ module Solargraph
42
58
  dirs
43
59
  end
44
60
 
61
+ # Get the version number of the installed core documentation that is
62
+ # the closest match for the current Ruby version.
63
+ #
64
+ # @return [String] The closest match
45
65
  def best_match
46
- available = versions
47
- available.each do |v|
48
- return v if Gem::Version.new(v) <= Gem::Version.new(RUBY_VERSION)
66
+ avail = versions
67
+ cur = Gem::Version.new(RUBY_VERSION)
68
+ avail.each do |v|
69
+ return v if Gem::Version.new(v) <= cur
49
70
  end
50
- return available.last
71
+ avail.last
51
72
  end
52
73
 
74
+ # Get a list of core documentation versions that are available for
75
+ # download.
76
+ #
77
+ # @return [Array<String>] The version numbers
53
78
  def available
54
79
  uri = URI.parse("#{SOURCE}/versions.json")
55
80
  response = Net::HTTP.get_response(uri)
56
81
  obj = JSON.parse(response.body)
57
- raise SourceNotAvailableError.new("Error connecting to #{SOURCE}") unless obj['status'] == 'ok'
82
+ raise SourceNotAvailableError, "Error connecting to #{SOURCE}" unless obj['status'] == 'ok'
58
83
  obj['cores']
59
84
  end
60
85
 
61
- def best_download
62
- rv = Gem::Version.new(RUBY_VERSION)
63
- available.each do |ver|
86
+ # Get the version number of core documentation available for download
87
+ # that is the closest match for the current Ruby version.
88
+ #
89
+ # @param current [String] The version to compare
90
+ # @return [String] The version number of the best match
91
+ def best_download current = RUBY_VERSION
92
+ rv = Gem::Version.new(current)
93
+ found = available
94
+ found.each do |ver|
64
95
  return ver if Gem::Version.new(ver) <= rv
65
96
  end
66
- obj['cores'].last
97
+ found.last
67
98
  end
68
99
 
100
+ # Get the path to a yardoc file for Ruby core documentation.
101
+ #
102
+ # @param ver [String] The version number (best match is default)
103
+ # @return [String] The path to the yardoc
69
104
  def yardoc_file(ver = best_match)
70
- raise ArgumentError.new("Invalid core yardoc version #{ver}") unless valid?(ver)
105
+ raise ArgumentError, "Invalid core yardoc version #{ver}" unless valid?(ver)
71
106
  File.join(cache_dir, ver, 'yardoc')
72
107
  end
73
108
 
74
- def yard_stdlib_file(ver = best_match)
75
- raise ArgumentError.new("Invalid core yardoc version #{ver}") unless valid?(ver)
109
+ # Get the path to a yardoc file for Ruby stdlib documentation.
110
+ #
111
+ # @param ver [String] The version number (best match is default)
112
+ # @return [String] The path to the yardoc
113
+ def yardoc_stdlib_file(ver = best_match)
114
+ raise ArgumentError, "Invalid core yardoc version #{ver}" unless valid?(ver)
76
115
  File.join(cache_dir, ver, 'yardoc-stdlib')
77
116
  end
78
117
 
118
+ # Download the specified version of core documentation.
119
+ #
120
+ # @param version [String]
121
+ # @return [void]
79
122
  def download version
80
123
  FileUtils.mkdir_p cache_dir
81
124
  uri = URI.parse("#{SOURCE}/#{version}.tar.gz")
@@ -85,6 +128,9 @@ module Solargraph
85
128
  install_archive zipfile
86
129
  end
87
130
 
131
+ # Reset the core documentation cache to the minimum requirement.
132
+ #
133
+ # @return [void]
88
134
  def clear
89
135
  FileUtils.rm_rf cache_dir, secure: true
90
136
  require_minimum
@@ -92,6 +138,10 @@ module Solargraph
92
138
 
93
139
  private
94
140
 
141
+ # Extract the specified archive to the core cache directory.
142
+ #
143
+ # @param filename [String]
144
+ # @return [void]
95
145
  def install_archive filename
96
146
  tar_extract = Gem::Package::TarReader.new(Zlib::GzipReader.open(filename))
97
147
  tar_extract.rewind