solargraph 0.23.6 → 0.24.0

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: c5b6ac3cd79425340ddf486c9b3447115553a374a3c782d8c8a103825b4461b9
4
- data.tar.gz: 4561568bb8075f955777c2a17a2a19a91c14b198df6c8f5798168b01abdeac19
3
+ metadata.gz: 102d3ab02b96835a5e3efc7a5ec5f07b2bd53c94660d16905f6c7264e6ac520a
4
+ data.tar.gz: 29aab3c067c6e9e95eeb5cf108ae5f71560f325aa02612bb69957840706e9d43
5
5
  SHA512:
6
- metadata.gz: 489953a5c791e8475b217b4033a502196a1d0aa0112f5d778ce2dba9ed488e773b867fb596ca02a1f81a6143b426d54007ab0448f979d1c9ad98344034fa96a1
7
- data.tar.gz: 390e4c2bf98f7733232f551749114229afb6e4f7271ac1792e2034cd223de6e0b636ab001281ad225b7f8267b0a8e1e48e927be0a88bab63a4ded3c7779e3bc3
6
+ metadata.gz: eed6ec2beee0afb8bcb8fc976f1d1487b5d79cd44acc1c27c89751f896da3ede2f8ec33da4d14278ea597e3308c3770cf6952d57dabf19de67d759b0a1464c1a
7
+ data.tar.gz: c7e7b79cf903a07e1237598a3d0f645aff86602128703af72531ee12d8bb7c1bf85a9445a097aec9f90ac8bd5dab9a9fcb24aa95fc6935250f24d017e8b85872
@@ -36,6 +36,7 @@ module Solargraph
36
36
  autoload :Page, 'solargraph/page'
37
37
  autoload :Library, 'solargraph/library'
38
38
  autoload :Diagnostics, 'solargraph/diagnostics'
39
+ autoload :ComplexType, 'solargraph/complex_type'
39
40
 
40
41
  YARDOC_PATH = File.join(File.realpath(File.dirname(__FILE__)), '..', 'yardoc')
41
42
  YARD_EXTENSION_FILE = File.join(File.realpath(File.dirname(__FILE__)), 'yard-solargraph.rb')
@@ -3,6 +3,9 @@ require 'set'
3
3
  require 'time'
4
4
 
5
5
  module Solargraph
6
+ # An aggregate provider for information about workspaces, sources, gems, and
7
+ # the Ruby core.
8
+ #
6
9
  class ApiMap
7
10
  autoload :Cache, 'solargraph/api_map/cache'
8
11
  autoload :SourceToYard, 'solargraph/api_map/source_to_yard'
@@ -120,13 +123,15 @@ module Solargraph
120
123
  virtualize source
121
124
  end
122
125
 
123
- # Refresh the ApiMap.
126
+ # Refresh the ApiMap. This method checks for pending changes before
127
+ # performing the refresh unless the `force` parameter is true.
124
128
  #
125
129
  # @param force [Boolean] Perform a refresh even if the map is not "stale."
130
+ # @return [Boolean] True if a refresh was performed.
126
131
  def refresh force = false
127
- return unless @force or changed?
132
+ return false unless force or changed?
128
133
  if force
129
- @api_map = ApiMap::Store.new(@sources)
134
+ refresh_store_and_maps
130
135
  else
131
136
  store.remove *(current_workspace_sources.reject{ |s| workspace.sources.include?(s) })
132
137
  @sources = workspace.sources
@@ -134,6 +139,7 @@ module Solargraph
134
139
  store.update *(@sources.select{ |s| @stime.nil? or s.stime > @stime })
135
140
  end
136
141
  @stime = Time.new
142
+ true
137
143
  end
138
144
 
139
145
  # True if a workspace file has been created, modified, or deleted since
@@ -319,10 +325,26 @@ module Solargraph
319
325
  elsif fragment.signature.include?('::') and !fragment.signature.include?('.')
320
326
  result.concat get_constants(fragment.base, fragment.namespace)
321
327
  else
322
- type = probe.infer_signature_type(fragment.base, fragment.named_path, fragment.locals)
323
- unless type.nil?
324
- namespace, scope = extract_namespace_and_scope(type)
325
- result.concat get_methods(namespace, scope: scope)
328
+ pins = probe.infer_signature_pins(fragment.base, fragment.named_path, fragment.locals)
329
+ unless pins.empty?
330
+ pin = pins.first
331
+ if pin.return_complex_types.any? and pin.return_complex_types.first.duck_type?
332
+ pin.return_complex_types.each do |t|
333
+ next unless t.duck_type?
334
+ result.push Pin::DuckMethod.new(pin.location, t.tag[1..-1])
335
+ end
336
+ result.concat(get_methods('Object', scope: :instance))
337
+ end
338
+ if result.empty?
339
+ pins.each do |pin|
340
+ type = pin.return_type
341
+ unless type.nil?
342
+ namespace, scope = extract_namespace_and_scope(type)
343
+ result.concat get_methods(namespace, scope: scope)
344
+ break
345
+ end
346
+ end
347
+ end
326
348
  end
327
349
  end
328
350
  end
@@ -428,6 +450,7 @@ module Solargraph
428
450
 
429
451
  # Get an array of all symbols in the workspace that match the query.
430
452
  #
453
+ # @param query [String]
431
454
  # @return [Array<Pin::Base>]
432
455
  def query_symbols query
433
456
  result = []
@@ -167,7 +167,8 @@ module Solargraph
167
167
  def virtual_new_pin new_pin, context_pin
168
168
  pin = Pin::Method.new(new_pin.location, new_pin.namespace, new_pin.name, new_pin.docstring, new_pin.scope, new_pin.visibility, new_pin.parameters)
169
169
  # @todo Smelly instance variable access.
170
- pin.instance_variable_set(:@return_type, context_pin.path)
170
+ # pin.instance_variable_set(:@return_type, context_pin.path)
171
+ pin.instance_variable_set(:@return_complex_types, ComplexType.parse(context_pin.path))
171
172
  pin
172
173
  end
173
174
 
@@ -175,9 +176,9 @@ module Solargraph
175
176
  # @param locals [Array<Solargraph::Pin::Base>]
176
177
  # @return [String]
177
178
  def resolve_pin_type pin, locals
178
- pin.return_type
179
179
  return pin.return_type unless pin.return_type.nil?
180
180
  return resolve_block_parameter(pin, locals) if pin.kind == Pin::BLOCK_PARAMETER
181
+ return resolve_method_parameter(pin) if pin.is_a?(Pin::MethodParameter)
181
182
  return resolve_variable(pin, locals) if pin.variable?
182
183
  nil
183
184
  end
@@ -210,6 +211,18 @@ module Solargraph
210
211
  nil
211
212
  end
212
213
 
214
+ def resolve_method_parameter pin
215
+ matches = api_map.get_methods(pin.namespace, scope: pin.scope, visibility: [:public, :private, :protected]).select{|p| p.name == pin.context.name}
216
+ matches.each do |m|
217
+ next unless pin.context.parameters == m.parameters
218
+ next if m.docstring.nil?
219
+ tag = m.docstring.tags(:param).select{|t| t.name == pin.name}.first
220
+ next if tag.nil? or tag.types.nil?
221
+ return tag.types[0]
222
+ end
223
+ nil
224
+ end
225
+
213
226
  def resolve_variable(pin, locals)
214
227
  return nil if pin.nil_assignment?
215
228
  # @todo Do we need the locals here?
@@ -0,0 +1,90 @@
1
+ module Solargraph
2
+ class ComplexType
3
+ # @return [String]
4
+ attr_reader :name
5
+
6
+ # @return [String]
7
+ attr_reader :substring
8
+
9
+ # @return [String]
10
+ attr_reader :tag
11
+
12
+ # @return [Array<ComplexType>]
13
+ attr_reader :subtypes
14
+
15
+ # Create a ComplexType with the specified name and an optional substring.
16
+ # The substring is parameter of a parameterized type, e.g., for the type
17
+ # `Array<String>`, the name is `Array` and the substring is `String`.
18
+ #
19
+ # @param name [String] The name of the type
20
+ # @param substring [String] The substring of the type
21
+ def initialize name, substring = ''
22
+ @name = name
23
+ @substring = substring
24
+ @tag = name
25
+ @tag += "<#{substring}>" unless substring.empty?
26
+ @subtypes = []
27
+ @subtypes.concat(ComplexType.parse(substring)) unless substring.empty?
28
+ end
29
+
30
+ # @return [Boolean]
31
+ def duck_type?
32
+ @duck_type ||= name.start_with?('#')
33
+ end
34
+
35
+ # @return [Boolean]
36
+ def nil_type?
37
+ @nil_type ||= (name.downcase == 'nil')
38
+ end
39
+
40
+ # @return [String]
41
+ def namespace
42
+ @namespace ||= 'Object' if duck_type?
43
+ @namespace ||= 'NilClass' if nil_type?
44
+ @namespace ||= ((name == 'Class' or name == 'Module') and !subtypes.empty?) ? subtypes.first.name : name
45
+ end
46
+
47
+ # @return [Symbol] :class or :instance
48
+ def scope
49
+ @scope ||= :instance if duck_type? or nil_type?
50
+ @scope ||= ((name == 'Class' or name == 'Module') and !subtypes.empty?) ? :class : :instance
51
+ end
52
+
53
+ class << self
54
+ # @param *strings [Array<String>] The type definitions to parse
55
+ # @return [Array<ComplexType>]
56
+ def parse *strings
57
+ types = []
58
+ strings.each do |type_string|
59
+ point_stack = 0
60
+ base = ''
61
+ subtype_string = ''
62
+ type_string.each_char do |char|
63
+ if char == '<'
64
+ point_stack += 1
65
+ next if point_stack == 1
66
+ elsif char == '>'
67
+ point_stack -= 1
68
+ raise 'Invalid close in type' if point_stack < 0
69
+ elsif char == ',' and point_stack == 0
70
+ types.push ComplexType.new base.strip, subtype_string.strip
71
+ base = ''
72
+ subtype_string = ''
73
+ next
74
+ end
75
+ if point_stack == 0 and char != '>'
76
+ base += char
77
+ elsif point_stack != 0
78
+ subtype_string += char
79
+ end
80
+ end
81
+ base.strip!
82
+ subtype_string.strip!
83
+ raise 'Unclosed subtype' if point_stack != 0
84
+ types.push ComplexType.new base, subtype_string
85
+ end
86
+ types
87
+ end
88
+ end
89
+ end
90
+ end
@@ -7,6 +7,7 @@ module Solargraph
7
7
  autoload :Severities, 'solargraph/diagnostics/severities'
8
8
  autoload :Rubocop, 'solargraph/diagnostics/rubocop'
9
9
  autoload :RequireNotFound, 'solargraph/diagnostics/require_not_found'
10
+ autoload :TypeNotDefined, 'solargraph/diagnostics/type_not_defined'
10
11
 
11
12
  class << self
12
13
  # Add a reporter with a name to identify it in .solargraph.yml files.
@@ -42,5 +43,6 @@ module Solargraph
42
43
 
43
44
  register 'rubocop', Rubocop
44
45
  register 'require_not_found', RequireNotFound
46
+ register 'type_not_defined', TypeNotDefined
45
47
  end
46
48
  end
@@ -1,9 +1,12 @@
1
1
  module Solargraph
2
2
  module Diagnostics
3
3
  class Base
4
- # @param source [Solargraph::Source]
5
- # @param source [Solargraph::ApiMap]
4
+ # Perform a diagnosis on a Source within the context of an ApiMap.
5
+ # The result is an array of hash objects that conform to the LSP's
6
+ # Diagnostic specification.
6
7
  #
8
+ # @param source [Solargraph::Source]
9
+ # @param api_map [Solargraph::ApiMap]
7
10
  # @return [Array<Hash>]
8
11
  def diagnose source, api_map
9
12
  end
@@ -0,0 +1,110 @@
1
+ module Solargraph
2
+ module Diagnostics
3
+ # TypeNotDefined reports methods with undefined return types, untagged
4
+ # parameters, and invalid param tags.
5
+ #
6
+ class TypeNotDefined < Base
7
+ def diagnose source, api_map
8
+ result = []
9
+ source.pins.select{|p| p.kind == Pin::METHOD or p.kind == Pin::ATTRIBUTE}.each do |pin|
10
+ result.concat check_return_type(pin, api_map, source)
11
+ result.concat check_param_types(pin, api_map, source)
12
+ result.concat check_param_tags(pin, api_map, source)
13
+ end
14
+ result
15
+ end
16
+
17
+ private
18
+
19
+ def check_return_type pin, api_map, source
20
+ return [] if (pin.name == 'initialize' and pin.scope == :instance) or (pin.name == 'new' and pin.scope == :class)
21
+ result = []
22
+ unless defined_return_type?(pin, api_map)
23
+ result.push(
24
+ range: extract_first_line(pin, source),
25
+ severity: Diagnostics::Severities::WARNING,
26
+ source: 'Solargraph',
27
+ message: "Method `#{pin.name}` has undefined return type."
28
+ )
29
+ end
30
+ result
31
+ end
32
+
33
+ def check_param_types pin, api_map, source
34
+ return [] if pin.name == 'new' and pin.scope == :class
35
+ result = []
36
+ pin.parameter_names.each do |par|
37
+ next if defined_param_type?(pin, par, api_map)
38
+ result.push(
39
+ range: extract_first_line(pin, source),
40
+ severity: Diagnostics::Severities::WARNING,
41
+ source: 'Solargraph',
42
+ message: "Method `#{pin.name}` has undefined param `#{par}`."
43
+ )
44
+ end
45
+ result
46
+ end
47
+
48
+ def check_param_tags pin, api_map, source
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
60
+ end
61
+ result
62
+ end
63
+
64
+ def extract_first_line pin, source
65
+ {
66
+ start: {
67
+ line: pin.location.range.start.line,
68
+ character: pin.location.range.start.character
69
+ },
70
+ end: {
71
+ line: pin.location.range.start.line,
72
+ character: last_character(pin.location.range.start, source)
73
+ }
74
+ }
75
+ end
76
+
77
+ def defined_return_type? pin, api_map
78
+ 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}
80
+ matches.shift
81
+ matches.any?{|m| !m.return_type.nil?}
82
+ end
83
+
84
+ def defined_param_type? pin, param, api_map
85
+ 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}
87
+ matches.shift
88
+ matches.each do |m|
89
+ next unless pin.parameter_names == m.parameter_names
90
+ return true if param_in_docstring?(param, m.docstring)
91
+ end
92
+ false
93
+ end
94
+
95
+ def param_in_docstring? param, docstring
96
+ return false if docstring.nil?
97
+ tags = docstring.tags(:param)
98
+ tags.any?{|t| t.name == param}
99
+ end
100
+
101
+ # @param position [Solargraph::Source::Position]
102
+ # @param source [Solargraph::Source]
103
+ # @return [Integer]
104
+ def last_character position, source
105
+ cursor = Source::Position.to_offset(source.code, position)
106
+ source.code.index(/[\r\n]/, cursor) || source.code.length
107
+ end
108
+ end
109
+ end
110
+ end
@@ -477,12 +477,18 @@ module Solargraph
477
477
  begin
478
478
  changed = false
479
479
  @change_queue.sort!{|a, b| a['textDocument']['version'] <=> b['textDocument']['version']}
480
+ pending = {}
481
+ @change_queue.each do |obj|
482
+ pending[obj['textDocument']['uri']] ||= 0
483
+ pending[obj['textDocument']['uri']] += 1
484
+ end
480
485
  @change_queue.delete_if do |change|
481
486
  filename = uri_to_file(change['textDocument']['uri'])
482
487
  source = library.checkout(filename)
483
488
  if change['textDocument']['version'] == source.version + change['contentChanges'].length
489
+ pending[change['textDocument']['uri']] -= 1
484
490
  updater = generate_updater(change)
485
- library.synchronize updater
491
+ library.synchronize updater, pending[change['textDocument']['uri']] == 0
486
492
  @diagnostics_queue.push change['textDocument']['uri']
487
493
  changed = true
488
494
  next true
@@ -491,8 +497,9 @@ module Solargraph
491
497
  # increments the version by one regardless of the number
492
498
  # of changes
493
499
  STDERR.puts "Warning: change applied to #{uri_to_file(change['textDocument']['uri'])} is possibly out of sync"
500
+ pending[change['textDocument']['uri']] -= 1
494
501
  updater = generate_updater(change)
495
- library.synchronize updater
502
+ library.synchronize updater, pending[change['textDocument']['uri']] == 0
496
503
  @diagnostics_queue.push change['textDocument']['uri']
497
504
  changed = true
498
505
  next true
@@ -521,7 +528,6 @@ module Solargraph
521
528
 
522
529
  def start_diagnostics_thread
523
530
  Thread.new do
524
- diagnoser = Diagnostics::Rubocop.new
525
531
  until stopped?
526
532
  sleep 0.1
527
533
  if !options['diagnostics']
@@ -4,7 +4,6 @@ module Solargraph
4
4
  class ExitNotification < Base
5
5
  def process
6
6
  host.stop
7
- exit
8
7
  end
9
8
  end
10
9
  end
@@ -3,7 +3,7 @@ module Solargraph
3
3
  module Message
4
4
  class Shutdown < Base
5
5
  def process
6
- set_result({})
6
+ # Nothing to do?
7
7
  end
8
8
  end
9
9
  end
@@ -2,7 +2,6 @@ module Solargraph
2
2
  # A library handles coordination between a Workspace and an ApiMap.
3
3
  #
4
4
  class Library
5
-
6
5
  # @param workspace [Solargraph::Workspace]
7
6
  def initialize workspace = Solargraph::Workspace.new(nil)
8
7
  @workspace = workspace
@@ -93,6 +92,8 @@ module Solargraph
93
92
  end
94
93
  end
95
94
 
95
+ # @param filename [String]
96
+ # @param version [Integer]
96
97
  def overwrite filename, version
97
98
  source = source_hash[filename]
98
99
  return if source.nil?
@@ -206,14 +207,22 @@ module Solargraph
206
207
  api_map.refresh force
207
208
  end
208
209
 
210
+ # @param query [String]
211
+ # @return [Array<YARD::CodeObject::Base>]
209
212
  def document query
210
213
  api_map.document query
211
214
  end
212
215
 
216
+ # @param query [String]
217
+ # @return [Array<String>]
213
218
  def search query
214
219
  api_map.search query
215
220
  end
216
221
 
222
+ # Get an array of all symbols in the workspace that match the query.
223
+ #
224
+ # @param query [String]
225
+ # @return [Array<Pin::Base>]
217
226
  def query_symbols query
218
227
  api_map.query_symbols query
219
228
  end
@@ -231,9 +240,9 @@ module Solargraph
231
240
  end
232
241
 
233
242
  # @param updater [Solargraph::Source::Updater]
234
- def synchronize updater
243
+ def synchronize updater, reparse = true
235
244
  source = read(updater.filename)
236
- source.synchronize updater
245
+ source.synchronize updater, reparse
237
246
  end
238
247
 
239
248
  # Get the current text of a file in the library.