solargraph 0.24.1 → 0.25.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/lib/solargraph/api_map.rb +93 -46
  3. data/lib/solargraph/api_map/cache.rb +51 -0
  4. data/lib/solargraph/api_map/probe.rb +23 -12
  5. data/lib/solargraph/api_map/source_to_yard.rb +2 -2
  6. data/lib/solargraph/api_map/store.rb +20 -9
  7. data/lib/solargraph/complex_type.rb +10 -1
  8. data/lib/solargraph/diagnostics/require_not_found.rb +1 -1
  9. data/lib/solargraph/diagnostics/rubocop.rb +35 -27
  10. data/lib/solargraph/diagnostics/type_not_defined.rb +10 -13
  11. data/lib/solargraph/language_server/host.rb +11 -11
  12. data/lib/solargraph/language_server/message.rb +0 -1
  13. data/lib/solargraph/language_server/message/base.rb +24 -4
  14. data/lib/solargraph/language_server/message/text_document/completion.rb +9 -16
  15. data/lib/solargraph/language_server/message/text_document/did_change.rb +0 -2
  16. data/lib/solargraph/language_server/message/text_document/formatting.rb +1 -10
  17. data/lib/solargraph/language_server/transport/socket.rb +0 -1
  18. data/lib/solargraph/language_server/transport/stdio.rb +0 -1
  19. data/lib/solargraph/pin.rb +1 -1
  20. data/lib/solargraph/pin/attribute.rb +4 -7
  21. data/lib/solargraph/pin/base.rb +113 -8
  22. data/lib/solargraph/pin/base_variable.rb +17 -25
  23. data/lib/solargraph/pin/block.rb +2 -2
  24. data/lib/solargraph/pin/block_parameter.rb +8 -10
  25. data/lib/solargraph/pin/constant.rb +2 -2
  26. data/lib/solargraph/pin/conversions.rb +8 -0
  27. data/lib/solargraph/pin/documenting.rb +2 -2
  28. data/lib/solargraph/pin/duck_method.rb +0 -1
  29. data/lib/solargraph/pin/local_variable.rb +8 -2
  30. data/lib/solargraph/pin/method.rb +26 -16
  31. data/lib/solargraph/pin/method_parameter.rb +15 -8
  32. data/lib/solargraph/pin/namespace.rb +2 -2
  33. data/lib/solargraph/pin/reference.rb +7 -0
  34. data/lib/solargraph/pin/yard_pin.rb +10 -0
  35. data/lib/solargraph/pin/yard_pin/constant.rb +14 -0
  36. data/lib/solargraph/pin/yard_pin/method.rb +35 -0
  37. data/lib/solargraph/pin/yard_pin/namespace.rb +27 -0
  38. data/lib/solargraph/pin/yard_pin/yard_mixin.rb +18 -0
  39. data/lib/solargraph/source.rb +59 -15
  40. data/lib/solargraph/source/mapper.rb +46 -99
  41. data/lib/solargraph/version.rb +1 -1
  42. data/lib/solargraph/workspace.rb +11 -2
  43. data/lib/solargraph/workspace/config.rb +47 -1
  44. data/lib/solargraph/yard_map.rb +103 -278
  45. data/lib/solargraph/yard_map/cache.rb +13 -38
  46. metadata +7 -3
  47. data/lib/solargraph/pin/yard_object.rb +0 -119
@@ -24,7 +24,7 @@ module Solargraph
24
24
  else
25
25
  code_object_map[pin.path] ||= YARD::CodeObjects::ModuleObject.new(root_code_object, pin.path)
26
26
  end
27
- code_object_map[pin.path].docstring = pin.docstring unless pin.docstring.nil?
27
+ code_object_map[pin.path].docstring = pin.docstring
28
28
  code_object_map[pin.path].files.push pin.location.filename
29
29
  end
30
30
  s.namespace_pins.each do |pin|
@@ -34,7 +34,7 @@ module Solargraph
34
34
  end
35
35
  s.method_pins.each do |pin|
36
36
  code_object_map[pin.path] ||= YARD::CodeObjects::MethodObject.new(code_object_at(pin.namespace), pin.name, pin.scope)
37
- code_object_map[pin.path].docstring = pin.docstring unless pin.docstring.nil?
37
+ code_object_map[pin.path].docstring = pin.docstring
38
38
  code_object_map[pin.path].visibility = pin.visibility || :public
39
39
  code_object_map[pin.path].files.push pin.location.filename
40
40
  code_object_map[pin.path].parameters = pin.parameters.map do |p|
@@ -4,8 +4,9 @@ module Solargraph
4
4
  class ApiMap
5
5
  class Store
6
6
  # @param sources [Array<Solargraph::Source>]
7
- def initialize sources
8
- update *sources
7
+ def initialize sources, yard_pins
8
+ inner_update *sources
9
+ pins.concat yard_pins
9
10
  index
10
11
  end
11
12
 
@@ -16,19 +17,20 @@ module Solargraph
16
17
 
17
18
  def remove *sources
18
19
  sources.each do |source|
19
- pins.delete_if { |pin| pin.filename == source.filename }
20
+ pins.delete_if { |pin| !pin.yard_pin? and pin.filename == source.filename }
20
21
  symbols.delete_if { |pin| pin.filename == source.filename }
21
22
  end
22
23
  index
23
24
  end
24
25
 
25
26
  def update *sources
26
- sources.each do |source|
27
- pins.delete_if { |pin| pin.filename == source.filename }
28
- symbols.delete_if { |pin| pin.filename == source.filename }
29
- pins.concat source.pins
30
- symbols.concat source.symbols
31
- end
27
+ inner_update *sources
28
+ index
29
+ end
30
+
31
+ def update_yard yard_pins
32
+ pins.delete_if(&:yard_pin?)
33
+ pins.concat yard_pins
32
34
  index
33
35
  end
34
36
 
@@ -154,6 +156,15 @@ module Solargraph
154
156
  namespaces.add pin.path if pin.kind == Pin::NAMESPACE and !pin.path.empty?
155
157
  end
156
158
  end
159
+
160
+ def inner_update *sources
161
+ sources.each do |source|
162
+ pins.delete_if { |pin| !pin.yard_pin? and pin.filename == source.filename }
163
+ symbols.delete_if { |pin| pin.filename == source.filename }
164
+ pins.concat source.pins
165
+ symbols.concat source.symbols
166
+ end
167
+ end
157
168
  end
158
169
  end
159
170
  end
@@ -50,6 +50,11 @@ module Solargraph
50
50
  @scope ||= ((name == 'Class' or name == 'Module') and !subtypes.empty?) ? :class : :instance
51
51
  end
52
52
 
53
+ def == other
54
+ return false unless self.class == other.class
55
+ tag == other.tag
56
+ end
57
+
53
58
  class << self
54
59
  # @param *strings [Array<String>] The type definitions to parse
55
60
  # @return [Array<ComplexType>]
@@ -57,6 +62,7 @@ module Solargraph
57
62
  types = []
58
63
  strings.each do |type_string|
59
64
  point_stack = 0
65
+ curly_stack = 0
60
66
  base = ''
61
67
  subtype_string = ''
62
68
  type_string.each_char do |char|
@@ -65,7 +71,10 @@ module Solargraph
65
71
  next if point_stack == 1
66
72
  elsif char == '>'
67
73
  point_stack -= 1
68
- raise 'Invalid close in type' if point_stack < 0
74
+ raise "Invalid close in type #{type_string}" if point_stack < 0
75
+ elsif char == '{'
76
+ # @todo Temporarily short-circuiting types with {}
77
+ break
69
78
  elsif char == ',' and point_stack == 0
70
79
  types.push ComplexType.new base.strip, subtype_string.strip
71
80
  base = ''
@@ -10,7 +10,7 @@ module Solargraph
10
10
  source.requires.each do |ref|
11
11
  refs[ref.name] = ref
12
12
  end
13
- api_map.yard_map.unresolved_requires.each do |r|
13
+ api_map.unresolved_requires.each do |r|
14
14
  next unless refs.has_key?(r)
15
15
  result.push(
16
16
  range: refs[r].location.range.to_hash,
@@ -1,5 +1,5 @@
1
- require 'open3'
2
- require 'shellwords'
1
+ require 'rubocop'
2
+ require 'stringio'
3
3
 
4
4
  module Solargraph
5
5
  module Diagnostics
@@ -15,43 +15,51 @@ module Solargraph
15
15
  'fatal' => Severities::ERROR
16
16
  }
17
17
 
18
- # The rubocop command
19
- #
20
- # @return [String]
21
- attr_reader :command
22
-
23
- def initialize(command = 'rubocop')
24
- @command = command
25
- end
26
-
27
18
  # @param source [Solargraph::Source]
28
19
  # @param api_map [Solargraph::ApiMap]
29
20
  # @return [Array<Hash>]
30
21
  def diagnose source, api_map
31
22
  begin
32
- text = source.code
33
- filename = source.filename
34
- raise DiagnosticsError, 'No command specified' if command.nil? or command.empty?
35
- cmd = "#{Shellwords.escape(command)} -f j"
36
- unless api_map.workspace.nil? or api_map.workspace.directory.nil?
37
- rc = File.join(api_map.workspace.directory, '.rubocop.yml')
38
- cmd += " -c #{Shellwords.escape(fix_drive_letter(rc))}" if File.file?(rc)
39
- end
40
- cmd += " -s #{Shellwords.escape(fix_drive_letter(filename))}"
41
- o, e, s = Open3.capture3(cmd, stdin_data: text)
42
- STDERR.puts e unless e.empty?
43
- raise DiagnosticsError, "Command '#{command}' is not available (gem exception)" if e.include?('Gem::Exception')
44
- raise DiagnosticsError, "RuboCop returned empty data" if o.empty?
45
- make_array JSON.parse(o)
23
+ options, paths = generate_options(api_map.workspace, source.filename, source.code)
24
+ runner = RuboCop::Runner.new(options, RuboCop::ConfigStore.new)
25
+ result = redirect_stdout{ runner.run(paths) }
26
+ make_array JSON.parse(result)
46
27
  rescue JSON::ParserError
47
28
  raise DiagnosticsError, 'RuboCop returned invalid data'
48
- rescue Errno::ENOENT
49
- raise DiagnosticsError, "Command '#{command}' is not available"
50
29
  end
51
30
  end
52
31
 
53
32
  private
54
33
 
34
+ # @param workspace [Solargraph::Workspace]
35
+ # @param filename [String]
36
+ # @param code [String]
37
+ # @return [Array]
38
+ def generate_options workspace, filename, code
39
+ args = ['-f', 'j']
40
+ unless workspace.nil? or workspace.directory.nil?
41
+ rc = File.join(workspace.directory, '.rubocop.yml')
42
+ args.push('-c', fix_drive_letter(rc)) if File.file?(rc)
43
+ end
44
+ args.push filename
45
+ options, paths = RuboCop::Options.new.parse(args)
46
+ options[:stdin] = code
47
+ [options, paths]
48
+ end
49
+
50
+ # @todo This is a smelly way to redirect output, but the RuboCop specs do the
51
+ # same thing.
52
+ # @return [String]
53
+ def redirect_stdout
54
+ redir = StringIO.new
55
+ $stdout = redir
56
+ yield if block_given?
57
+ $stdout = STDOUT
58
+ redir.string
59
+ end
60
+
61
+ # @param resp [Hash]
62
+ # @return [Array<Hash>]
55
63
  def make_array resp
56
64
  diagnostics = []
57
65
  resp['files'].each do |file|
@@ -47,16 +47,14 @@ module Solargraph
47
47
 
48
48
  def check_param_tags pin, api_map, source
49
49
  result = []
50
- unless pin.docstring.nil?
51
- pin.docstring.tags(:param).each do |par|
52
- next if pin.parameter_names.include?(par.name)
53
- result.push(
54
- range: extract_first_line(pin, source),
55
- severity: Diagnostics::Severities::WARNING,
56
- source: 'Solargraph',
57
- message: "Method `#{pin.name}` has mistagged param `#{par.name}`."
58
- )
59
- end
50
+ pin.docstring.tags(:param).each do |par|
51
+ next if pin.parameter_names.include?(par.name)
52
+ result.push(
53
+ range: extract_first_line(pin, source),
54
+ severity: Diagnostics::Severities::WARNING,
55
+ source: 'Solargraph',
56
+ message: "Method `#{pin.name}` has mistagged param `#{par.name}`."
57
+ )
60
58
  end
61
59
  result
62
60
  end
@@ -76,14 +74,14 @@ module Solargraph
76
74
 
77
75
  def defined_return_type? pin, api_map
78
76
  return true unless pin.return_type.nil?
79
- matches = api_map.get_methods(pin.namespace, scope: pin.scope, visibility: [:public, :private, :protected]).select{|p| p.name == pin.name}
77
+ matches = api_map.get_method_stack(pin.namespace, pin.name, scope: pin.scope)
80
78
  matches.shift
81
79
  matches.any?{|m| !m.return_type.nil?}
82
80
  end
83
81
 
84
82
  def defined_param_type? pin, param, api_map
85
83
  return true if param_in_docstring?(param, pin.docstring)
86
- matches = api_map.get_methods(pin.namespace, scope: pin.scope, visibility: [:public, :private, :protected]).select{|p| p.name == pin.name}
84
+ matches = api_map.get_method_stack(pin.namespace, pin.name, scope: pin.scope)
87
85
  matches.shift
88
86
  matches.each do |m|
89
87
  next unless pin.parameter_names == m.parameter_names
@@ -93,7 +91,6 @@ module Solargraph
93
91
  end
94
92
 
95
93
  def param_in_docstring? param, docstring
96
- return false if docstring.nil?
97
94
  tags = docstring.tags(:param)
98
95
  tags.any?{|t| t.name == param}
99
96
  end
@@ -492,17 +492,17 @@ module Solargraph
492
492
  @diagnostics_queue.push change['textDocument']['uri']
493
493
  changed = true
494
494
  next true
495
- elsif change['textDocument']['version'] == source.version + 1 #and change['contentChanges'].length == 0
496
- # HACK: This condition fixes the fact that formatting
497
- # increments the version by one regardless of the number
498
- # of changes
499
- STDERR.puts "Warning: change applied to #{uri_to_file(change['textDocument']['uri'])} is possibly out of sync"
500
- pending[change['textDocument']['uri']] -= 1
501
- updater = generate_updater(change)
502
- library.synchronize updater, pending[change['textDocument']['uri']] == 0
503
- @diagnostics_queue.push change['textDocument']['uri']
504
- changed = true
505
- next true
495
+ # elsif change['textDocument']['version'] == source.version + 1 #and change['contentChanges'].length == 0
496
+ # # HACK: This condition fixes the fact that formatting
497
+ # # increments the version by one regardless of the number
498
+ # # of changes
499
+ # STDERR.puts "Warning: change applied to #{uri_to_file(change['textDocument']['uri'])} is possibly out of sync"
500
+ # pending[change['textDocument']['uri']] -= 1
501
+ # updater = generate_updater(change)
502
+ # library.synchronize updater, pending[change['textDocument']['uri']] == 0
503
+ # @diagnostics_queue.push change['textDocument']['uri']
504
+ # changed = true
505
+ # next true
506
506
  elsif change['textDocument']['version'] <= source.version
507
507
  # @todo Is deleting outdated changes correct behavior?
508
508
  STDERR.puts "Warning: outdated change to #{change['textDocument']['uri']} was ignored"
@@ -1,6 +1,5 @@
1
1
  require 'solargraph'
2
2
  require 'uri'
3
- require 'thread'
4
3
 
5
4
  module Solargraph
6
5
  module LanguageServer
@@ -4,13 +4,27 @@ module Solargraph
4
4
  class Base
5
5
  # @return [Solargraph::LanguageServer::Host]
6
6
  attr_reader :host
7
+
8
+ # @return [Integer]
7
9
  attr_reader :id
10
+
11
+ # @return [Hash]
8
12
  attr_reader :request
13
+
14
+ # @return [String]
9
15
  attr_reader :method
16
+
17
+ # @return [Hash]
10
18
  attr_reader :params
19
+
20
+ # @return [Hash, Array, nil]
11
21
  attr_reader :result
22
+
23
+ # @return [Hash, nil]
12
24
  attr_reader :error
13
25
 
26
+ # @param host [Solargraph::LanguageServer::Host]
27
+ # @param request [Hash]
14
28
  def initialize host, request
15
29
  @host = host
16
30
  @id = request['id'].freeze
@@ -20,16 +34,21 @@ module Solargraph
20
34
  post_initialize
21
35
  end
22
36
 
23
- def post_initialize
24
- end
37
+ # @return [void]
38
+ def post_initialize; end
25
39
 
26
- def process
27
- end
40
+ # @return [void]
41
+ def process; end
28
42
 
43
+ # @param data [Hash, Array, nil]
44
+ # @return [void]
29
45
  def set_result data
30
46
  @result = data
31
47
  end
32
48
 
49
+ # @param code [Integer] See Solargraph::LanguageServer::ErrorCodes
50
+ # @param message [String]
51
+ # @return [void]
33
52
  def set_error code, message
34
53
  @error = {
35
54
  code: code,
@@ -37,6 +56,7 @@ module Solargraph
37
56
  }
38
57
  end
39
58
 
59
+ # @return [void]
40
60
  def send_response
41
61
  unless id.nil? or host.cancel?(id)
42
62
  response = {
@@ -6,25 +6,16 @@ module Solargraph
6
6
  module TextDocument
7
7
  class Completion < Base
8
8
  def process
9
- start = Time.now
10
- processed = false
11
- until processed
12
- if host.changing?(params['textDocument']['uri'])
13
- if Time.now - start > 1
14
- # set_error Solargraph::LanguageServer::ErrorCodes::INTERNAL_ERROR, 'Completion request timed out'
15
- set_result empty_result
16
- processed = true
17
- end
18
- else
19
- inner_process
20
- processed = true
21
- end
22
- sleep 0.1 unless processed
9
+ if host.changing?(params['textDocument']['uri'])
10
+ set_result empty_result(true)
11
+ else
12
+ inner_process
23
13
  end
24
14
  end
25
15
 
26
16
  private
27
17
 
18
+ # @return [void]
28
19
  def inner_process
29
20
  filename = uri_to_file(params['textDocument']['uri'])
30
21
  line = params['position']['line']
@@ -56,9 +47,11 @@ module Solargraph
56
47
  end
57
48
  end
58
49
 
59
- def empty_result
50
+ # @param incomplete [Boolean]
51
+ # @return [Hash]
52
+ def empty_result incomplete = false
60
53
  {
61
- isIncomplete: false,
54
+ isIncomplete: incomplete,
62
55
  items: []
63
56
  }
64
57
  end
@@ -1,5 +1,3 @@
1
- require 'thread'
2
-
3
1
  module Solargraph
4
2
  module LanguageServer
5
3
  module Message
@@ -17,16 +17,7 @@ module Solargraph
17
17
  set_result(
18
18
  [
19
19
  {
20
- range: {
21
- start: {
22
- line: 0,
23
- character: 0
24
- },
25
- end: {
26
- line: original.lines.length,
27
- character: 0
28
- }
29
- },
20
+ range: nil,
30
21
  newText: formatted
31
22
  }
32
23
  ]
@@ -21,7 +21,6 @@ module Solargraph
21
21
  message.send_response
22
22
  tmp = @host.flush
23
23
  send_data tmp unless tmp.empty?
24
- GC.start unless request['method'] == 'textDocument/didChange'
25
24
  end
26
25
  end
27
26
 
@@ -49,7 +49,6 @@ module Solargraph
49
49
  message.send_response
50
50
  tmp = @host.flush
51
51
  send_data tmp unless tmp.empty?
52
- GC.start unless request['method'] == 'textDocument/didChange'
53
52
  end
54
53
  end
55
54
 
@@ -13,7 +13,6 @@ module Solargraph
13
13
  autoload :Constant, 'solargraph/pin/constant'
14
14
  autoload :Symbol, 'solargraph/pin/symbol'
15
15
  autoload :Namespace, 'solargraph/pin/namespace'
16
- autoload :YardObject, 'solargraph/pin/yard_object'
17
16
  autoload :Keyword, 'solargraph/pin/keyword'
18
17
  autoload :MethodParameter, 'solargraph/pin/method_parameter'
19
18
  autoload :BlockParameter, 'solargraph/pin/block_parameter'
@@ -23,6 +22,7 @@ module Solargraph
23
22
  autoload :Localized, 'solargraph/pin/localized'
24
23
  autoload :ProxyMethod, 'solargraph/pin/proxy_method'
25
24
  autoload :DuckMethod, 'solargraph/pin/duck_method'
25
+ autoload :YardPin, 'solargraph/pin/yard_pin'
26
26
 
27
27
  ATTRIBUTE = 1
28
28
  CLASS_VARIABLE = 2