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 +4 -4
- data/lib/solargraph.rb +1 -0
- data/lib/solargraph/api_map.rb +30 -7
- data/lib/solargraph/api_map/probe.rb +15 -2
- data/lib/solargraph/complex_type.rb +90 -0
- data/lib/solargraph/diagnostics.rb +2 -0
- data/lib/solargraph/diagnostics/base.rb +5 -2
- data/lib/solargraph/diagnostics/type_not_defined.rb +110 -0
- data/lib/solargraph/language_server/host.rb +9 -3
- data/lib/solargraph/language_server/message/exit_notification.rb +0 -1
- data/lib/solargraph/language_server/message/shutdown.rb +1 -1
- data/lib/solargraph/library.rb +12 -3
- data/lib/solargraph/pin.rb +1 -0
- data/lib/solargraph/pin/attribute.rb +12 -5
- data/lib/solargraph/pin/base.rb +47 -3
- data/lib/solargraph/pin/base_variable.rb +25 -4
- data/lib/solargraph/pin/block_parameter.rb +20 -11
- data/lib/solargraph/pin/duck_method.rb +15 -0
- data/lib/solargraph/pin/method.rb +16 -26
- data/lib/solargraph/pin/method_parameter.rb +12 -9
- data/lib/solargraph/pin/namespace.rb +2 -2
- data/lib/solargraph/pin/proxy_method.rb +5 -6
- data/lib/solargraph/pin/yard_object.rb +6 -2
- data/lib/solargraph/source.rb +26 -3
- data/lib/solargraph/source/change.rb +6 -0
- data/lib/solargraph/source/fragment.rb +31 -7
- data/lib/solargraph/source/location.rb +7 -0
- data/lib/solargraph/source/mapper.rb +28 -3
- data/lib/solargraph/source/position.rb +5 -0
- data/lib/solargraph/source/range.rb +5 -0
- data/lib/solargraph/version.rb +1 -1
- data/lib/solargraph/yard_map.rb +26 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 102d3ab02b96835a5e3efc7a5ec5f07b2bd53c94660d16905f6c7264e6ac520a
|
4
|
+
data.tar.gz: 29aab3c067c6e9e95eeb5cf108ae5f71560f325aa02612bb69957840706e9d43
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eed6ec2beee0afb8bcb8fc976f1d1487b5d79cd44acc1c27c89751f896da3ede2f8ec33da4d14278ea597e3308c3770cf6952d57dabf19de67d759b0a1464c1a
|
7
|
+
data.tar.gz: c7e7b79cf903a07e1237598a3d0f645aff86602128703af72531ee12d8bb7c1bf85a9445a097aec9f90ac8bd5dab9a9fcb24aa95fc6935250f24d017e8b85872
|
data/lib/solargraph.rb
CHANGED
@@ -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')
|
data/lib/solargraph/api_map.rb
CHANGED
@@ -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
|
132
|
+
return false unless force or changed?
|
128
133
|
if force
|
129
|
-
|
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
|
-
|
323
|
-
unless
|
324
|
-
|
325
|
-
|
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
|
-
#
|
5
|
-
#
|
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']
|
data/lib/solargraph/library.rb
CHANGED
@@ -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.
|