solargraph 0.30.2 → 0.31.0

Sign up to get free protection for your applications and to get access to all the features.
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