solargraph 0.18.3 → 0.19.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/api_map/probe.rb +222 -0
- data/lib/solargraph/api_map/source_to_yard.rb +3 -3
- data/lib/solargraph/api_map/store.rb +135 -0
- data/lib/solargraph/api_map.rb +169 -609
- data/lib/solargraph/diagnostics/rubocop.rb +4 -4
- data/lib/solargraph/language_server/host.rb +53 -19
- data/lib/solargraph/language_server/message/extended/document.rb +1 -1
- data/lib/solargraph/language_server/message/extended/search.rb +1 -1
- data/lib/solargraph/language_server/message/method_not_found.rb +1 -1
- data/lib/solargraph/language_server/message/text_document/definition.rb +2 -15
- data/lib/solargraph/language_server/message/text_document/document_symbol.rb +2 -15
- data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +3 -15
- data/lib/solargraph/language_server/message_types.rb +10 -0
- data/lib/solargraph/language_server.rb +1 -0
- data/lib/solargraph/library.rb +8 -0
- data/lib/solargraph/node_methods.rb +6 -1
- data/lib/solargraph/page.rb +2 -1
- data/lib/solargraph/pin/attribute.rb +8 -12
- data/lib/solargraph/pin/base.rb +20 -95
- data/lib/solargraph/pin/base_variable.rb +15 -74
- data/lib/solargraph/pin/block.rb +21 -0
- data/lib/solargraph/pin/block_parameter.rb +30 -44
- data/lib/solargraph/pin/class_variable.rb +3 -0
- data/lib/solargraph/pin/constant.rb +4 -8
- data/lib/solargraph/pin/conversions.rb +4 -3
- data/lib/solargraph/pin/documenting.rb +27 -0
- data/lib/solargraph/pin/global_variable.rb +3 -0
- data/lib/solargraph/pin/instance_variable.rb +5 -4
- data/lib/solargraph/pin/local_variable.rb +8 -15
- data/lib/solargraph/pin/localized.rb +12 -0
- data/lib/solargraph/pin/method.rb +6 -67
- data/lib/solargraph/pin/method_parameter.rb +24 -11
- data/lib/solargraph/pin/namespace.rb +26 -35
- data/lib/solargraph/pin/reference.rb +15 -8
- data/lib/solargraph/pin/symbol.rb +34 -3
- data/lib/solargraph/pin/yard_object.rb +11 -4
- data/lib/solargraph/pin.rb +16 -2
- data/lib/solargraph/server.rb +2 -2
- data/lib/solargraph/source/change.rb +10 -13
- data/lib/solargraph/source/fragment.rb +42 -94
- data/lib/solargraph/source/location.rb +13 -0
- data/lib/solargraph/source/mapper.rb +426 -0
- data/lib/solargraph/source/position.rb +1 -0
- data/lib/solargraph/source/range.rb +11 -3
- data/lib/solargraph/source.rb +93 -284
- data/lib/solargraph/version.rb +1 -1
- data/lib/solargraph/views/_method.erb +59 -60
- data/lib/solargraph/views/_name_type_tag.erb +10 -0
- data/lib/solargraph/views/_namespace.erb +26 -26
- data/lib/solargraph/views/document.erb +23 -16
- data/lib/solargraph/views/layout.erb +38 -10
- data/lib/solargraph/views/search.erb +12 -11
- data/lib/solargraph/workspace/config.rb +27 -6
- data/lib/solargraph/workspace.rb +10 -2
- data/lib/solargraph.rb +10 -2
- data/lib/yard-solargraph.rb +3 -0
- metadata +25 -20
- data/lib/solargraph/pin/directed/attribute.rb +0 -20
- data/lib/solargraph/pin/directed/method.rb +0 -22
- data/lib/solargraph/pin/directed.rb +0 -9
- data/lib/solargraph/pin/parameter.rb +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7584a4d20d5562279e2e86887fe56a448776ba91
|
4
|
+
data.tar.gz: bd353cd7bf5140f80f0a53155bfc03cb0dc1d73b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eb78c3e91842800f4646fc5c9f96371914226c17a6b3350b1bf4f5ec2e4e9b00801fa39034b52c859ee63613abef3d453aa87d0a18235e6bcb1093d62573de1a
|
7
|
+
data.tar.gz: 4cef990fe1e4935f478cac2a4b24067ca668f2c83de44ffb9d09e5b3faf46179ef4c2ec9fccd88681f07a4921fb58b6d2dba1ce06fb500e04ac9ab77522e71ee
|
@@ -0,0 +1,222 @@
|
|
1
|
+
module Solargraph
|
2
|
+
class ApiMap
|
3
|
+
class Probe
|
4
|
+
class VirtualPin
|
5
|
+
attr_reader :return_type
|
6
|
+
def initialize return_type
|
7
|
+
@return_type = return_type
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# @return [Solargraph::ApiMap]
|
12
|
+
attr_reader :api_map
|
13
|
+
|
14
|
+
def initialize api_map
|
15
|
+
@api_map = api_map
|
16
|
+
end
|
17
|
+
|
18
|
+
# Get all matching pins for the signature.
|
19
|
+
#
|
20
|
+
# @return [Array<Pin::Base>]
|
21
|
+
def infer_signature_pins signature, context_pin, locals
|
22
|
+
return [] if signature.nil? or signature.empty?
|
23
|
+
base, rest = signature.split('.', 2)
|
24
|
+
return infer_word_pins(base, context_pin, locals) if rest.nil?
|
25
|
+
pins = infer_word_pins(base, context_pin, locals).map do |pin|
|
26
|
+
next pin unless pin.return_type.nil?
|
27
|
+
type = resolve_pin_type(pin)
|
28
|
+
VirtualPin.new(type)
|
29
|
+
end
|
30
|
+
return [] if pins.empty?
|
31
|
+
rest = rest.split('.')
|
32
|
+
last = rest.pop
|
33
|
+
rest.each do |meth|
|
34
|
+
found = nil
|
35
|
+
pins.each do |pin|
|
36
|
+
found = infer_method_name_pins(meth, pin)
|
37
|
+
next if found.empty?
|
38
|
+
pins = found
|
39
|
+
break
|
40
|
+
end
|
41
|
+
return [] if found.nil?
|
42
|
+
end
|
43
|
+
pins.each do |pin|
|
44
|
+
found = infer_method_name_pins(last, pin)
|
45
|
+
return found unless found.empty?
|
46
|
+
end
|
47
|
+
[]
|
48
|
+
end
|
49
|
+
|
50
|
+
# Get the return type for the signature.
|
51
|
+
#
|
52
|
+
# @return [String]
|
53
|
+
def infer_signature_type signature, context_pin, locals
|
54
|
+
pins = infer_signature_pins(signature, context_pin, locals)
|
55
|
+
pins.each do |pin|
|
56
|
+
type = resolve_pin_type(pin)
|
57
|
+
return qualify(type, pin.named_context) unless type.nil?
|
58
|
+
end
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# Word search is ALWAYS internal
|
65
|
+
def infer_word_pins word, context_pin, locals
|
66
|
+
return [] if word.empty?
|
67
|
+
lvars = locals.select{|pin| pin.name == word}
|
68
|
+
return lvars unless lvars.empty?
|
69
|
+
namespace, scope = extract_namespace_and_scope_from_pin(context_pin)
|
70
|
+
return api_map.pins.select{|pin| word_matches_context?(word, namespace, scope, pin)} if variable_name?(word)
|
71
|
+
result = []
|
72
|
+
result.concat api_map.get_path_suggestions(word)
|
73
|
+
result.concat api_map.get_methods(namespace, scope: scope, visibility: [:public, :private, :protected]).select{|pin| pin.name == word} unless word.include?('::')
|
74
|
+
result.concat api_map.get_constants('', namespace).select{|pin| pin.name == word}
|
75
|
+
result
|
76
|
+
end
|
77
|
+
|
78
|
+
# Method name search is external by default
|
79
|
+
def infer_method_name_pins method_name, context_pin, internal = false
|
80
|
+
namespace, scope = extract_namespace_and_scope(context_pin.return_type)
|
81
|
+
visibility = [:public]
|
82
|
+
visibility.push :protected, :private if internal
|
83
|
+
result = api_map.get_methods(namespace, scope: scope, visibility: visibility).select{|pin| pin.name == method_name}
|
84
|
+
# @todo This needs more rules. Probably need to update YardObject for it.
|
85
|
+
return result if result.empty?
|
86
|
+
return result unless method_name == 'new' and result.first.path == 'Class#new'
|
87
|
+
result.unshift virtual_new_pin(result.first, context_pin)
|
88
|
+
result
|
89
|
+
end
|
90
|
+
|
91
|
+
# Word and context matching rules for ApiMap source pins.
|
92
|
+
#
|
93
|
+
# @return [Boolean]
|
94
|
+
def word_matches_context? word, namespace, scope, pin
|
95
|
+
return false unless word == pin.name
|
96
|
+
return true if pin.kind == Pin::NAMESPACE and pin.path == namespace and scope == :class
|
97
|
+
return true if pin.kind == Pin::METHOD and pin.namespace == namespace and pin.scope == scope
|
98
|
+
# @todo Handle instance variables, class variables, etc. in various ways
|
99
|
+
pin.namespace == namespace and pin.scope == scope
|
100
|
+
end
|
101
|
+
|
102
|
+
# Fully qualify the namespace in a type.
|
103
|
+
#
|
104
|
+
# @return [String]
|
105
|
+
def qualify type, context
|
106
|
+
rns, rsc = extract_namespace_and_scope(type)
|
107
|
+
res = api_map.qualify(rns, context)
|
108
|
+
return res if rsc == :instance
|
109
|
+
type.sub(/<#{rns}>/, "<#{res}>")
|
110
|
+
end
|
111
|
+
|
112
|
+
# Extract a namespace from a type.
|
113
|
+
#
|
114
|
+
# @example
|
115
|
+
# extract_namespace('String') => 'String'
|
116
|
+
# extract_namespace('Class<String>') => 'String'
|
117
|
+
#
|
118
|
+
# @return [String]
|
119
|
+
def extract_namespace type
|
120
|
+
extract_namespace_and_scope(type)[0]
|
121
|
+
end
|
122
|
+
|
123
|
+
# Extract a namespace and a scope (:instance or :class) from a type.
|
124
|
+
#
|
125
|
+
# @example
|
126
|
+
# extract_namespace('String') #=> ['String', :instance]
|
127
|
+
# extract_namespace('Class<String>') #=> ['String', :class]
|
128
|
+
# extract_namespace('Module<Enumerable') #=> ['Enumberable', :class]
|
129
|
+
#
|
130
|
+
# @return [Array] The namespace (String) and scope (Symbol).
|
131
|
+
def extract_namespace_and_scope type
|
132
|
+
scope = :instance
|
133
|
+
result = type.to_s.gsub(/<.*$/, '')
|
134
|
+
if (result == 'Class' or result == 'Module') and type.include?('<')
|
135
|
+
result = type.match(/<([a-z0-9:_]*)/i)[1]
|
136
|
+
scope = :class
|
137
|
+
end
|
138
|
+
[result, scope]
|
139
|
+
end
|
140
|
+
|
141
|
+
# Extract a namespace and a scope from a pin. For now, the pin must
|
142
|
+
# be either a method or a namespace. It probably makes sense to support
|
143
|
+
# blocks at some point.
|
144
|
+
#
|
145
|
+
# @return [Array] The namespace (String) and scope (Symbol).
|
146
|
+
def extract_namespace_and_scope_from_pin pin
|
147
|
+
return [pin.namespace, pin.scope] if pin.kind == Pin::METHOD
|
148
|
+
return [pin.namespace, :class] if pin.kind == Pin::NAMESPACE
|
149
|
+
# @todo Is :class appropriate for blocks?
|
150
|
+
return [pin.namespace, :class] if pin.kind == Pin::BLOCK
|
151
|
+
raise "Unable to extract namespace and scope from #{pin.path}"
|
152
|
+
end
|
153
|
+
|
154
|
+
# Determine whether or not the word represents a variable. This method
|
155
|
+
# is used to keep the probe from performing unnecessary constant and
|
156
|
+
# method searches.
|
157
|
+
#
|
158
|
+
# @return [Boolean]
|
159
|
+
def variable_name? word
|
160
|
+
word.start_with?('@') or word.start_with?('$')
|
161
|
+
end
|
162
|
+
|
163
|
+
# Create a `new` pin to facilitate type inference. This is necessary for
|
164
|
+
# classes from YARD and classes in the namespace that do not have an
|
165
|
+
# `initialize` method.
|
166
|
+
#
|
167
|
+
# @return [Pin::Method]
|
168
|
+
def virtual_new_pin new_pin, context_pin
|
169
|
+
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)
|
170
|
+
# @todo Smelly instance variable access.
|
171
|
+
pin.instance_variable_set(:@return_type, context_pin.path)
|
172
|
+
pin
|
173
|
+
end
|
174
|
+
|
175
|
+
def resolve_pin_type pin
|
176
|
+
pin.return_type
|
177
|
+
return pin.return_type unless pin.return_type.nil?
|
178
|
+
return resolve_block_parameter(pin) if pin.kind == Pin::BLOCK_PARAMETER
|
179
|
+
return resolve_variable(pin) if pin.variable?
|
180
|
+
nil
|
181
|
+
end
|
182
|
+
|
183
|
+
def resolve_block_parameter pin
|
184
|
+
return pin.return_type unless pin.return_type.nil?
|
185
|
+
signature = pin.block.receiver
|
186
|
+
# @todo Not sure if assuming the first pin is good here
|
187
|
+
meth = @api_map.probe.infer_signature_pins(signature, pin.block, []).first
|
188
|
+
return nil if meth.nil?
|
189
|
+
if (Solargraph::CoreFills::METHODS_WITH_YIELDPARAM_SUBTYPES.include?(meth.path))
|
190
|
+
base = signature.split('.')[0..-2].join('.')
|
191
|
+
return nil if base.nil? or base.empty?
|
192
|
+
# @todo Not sure if assuming the first pin is good here
|
193
|
+
bmeth = @api_map.probe.infer_signature_pins(base, pin.block, []).first
|
194
|
+
return nil if bmeth.nil?
|
195
|
+
subtypes = get_subtypes(bmeth.return_type)
|
196
|
+
return subtypes[0]
|
197
|
+
else
|
198
|
+
unless meth.docstring.nil?
|
199
|
+
yps = meth.docstring.tags(:yieldparam)
|
200
|
+
unless yps[pin.index].nil? or yps[pin.index].types.nil? or yps[pin.index].types.empty?
|
201
|
+
return yps[pin.index].types[0]
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
nil
|
206
|
+
end
|
207
|
+
|
208
|
+
def resolve_variable(pin)
|
209
|
+
return nil if pin.nil_assignment?
|
210
|
+
# @todo Do we need the locals here?
|
211
|
+
return infer_signature_type(pin.signature, pin.context, [])
|
212
|
+
end
|
213
|
+
|
214
|
+
def get_subtypes type
|
215
|
+
return nil if type.nil?
|
216
|
+
match = type.match(/<([a-z0-9_:, ]*)>/i)
|
217
|
+
return [] if match.nil?
|
218
|
+
match[1].split(',').map(&:strip)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -25,7 +25,7 @@ module Solargraph
|
|
25
25
|
code_object_map[pin.path] ||= YARD::CodeObjects::ModuleObject.new(root_code_object, pin.path)
|
26
26
|
end
|
27
27
|
code_object_map[pin.path].docstring = pin.docstring unless pin.docstring.nil?
|
28
|
-
code_object_map[pin.path].files.push pin.
|
28
|
+
code_object_map[pin.path].files.push pin.location.filename
|
29
29
|
end
|
30
30
|
s.namespace_pins.each do |pin|
|
31
31
|
pin.include_references.each do |ref|
|
@@ -35,13 +35,13 @@ module Solargraph
|
|
35
35
|
s.attribute_pins.each do |pin|
|
36
36
|
code_object_map[pin.path] ||= YARD::CodeObjects::MethodObject.new(code_object_at(pin.namespace), pin.name, :instance)
|
37
37
|
code_object_map[pin.path].docstring = pin.docstring unless pin.docstring.nil?
|
38
|
-
code_object_map[pin.path].files.push pin.
|
38
|
+
code_object_map[pin.path].files.push pin.location.filename
|
39
39
|
#code_object_map[pin.path].parameters = []
|
40
40
|
end
|
41
41
|
s.method_pins.each do |pin|
|
42
42
|
code_object_map[pin.path] ||= YARD::CodeObjects::MethodObject.new(code_object_at(pin.namespace), pin.name, pin.scope)
|
43
43
|
code_object_map[pin.path].docstring = pin.docstring unless pin.docstring.nil?
|
44
|
-
code_object_map[pin.path].files.push pin.
|
44
|
+
code_object_map[pin.path].files.push pin.location.filename
|
45
45
|
code_object_map[pin.path].parameters = pin.parameters.map do |p|
|
46
46
|
n = p.match(/^[a-z0-9_]*:?/i)[0]
|
47
47
|
v = nil
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Solargraph
|
4
|
+
class ApiMap
|
5
|
+
class Store
|
6
|
+
# @param sources [Solargraph::Source]
|
7
|
+
def initialize sources
|
8
|
+
update *sources
|
9
|
+
index
|
10
|
+
end
|
11
|
+
|
12
|
+
def pins
|
13
|
+
@pins ||= []
|
14
|
+
end
|
15
|
+
|
16
|
+
def remove *sources
|
17
|
+
sources.each do |source|
|
18
|
+
pins.delete_if { |pin| pin.filename == source.filename }
|
19
|
+
symbols.delete_if { |pin| pin.filename == source.filename }
|
20
|
+
end
|
21
|
+
index
|
22
|
+
end
|
23
|
+
|
24
|
+
def update *sources
|
25
|
+
sources.each do |source|
|
26
|
+
pins.delete_if { |pin| pin.filename == source.filename }
|
27
|
+
symbols.delete_if { |pin| pin.filename == source.filename }
|
28
|
+
pins.concat source.pins
|
29
|
+
symbols.concat source.symbols
|
30
|
+
end
|
31
|
+
index
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_constants fqns, visibility = [:public]
|
35
|
+
namespace_pins(fqns).select { |pin|
|
36
|
+
!pin.name.empty? and (pin.kind == Pin::NAMESPACE or pin.kind == Pin::CONSTANT) and visibility.include?(pin.visibility)
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_methods fqns, scope: :instance, visibility: [:public]
|
41
|
+
namespace_pins(fqns).select{ |pin|
|
42
|
+
pin.kind == Pin::METHOD and (pin.scope == scope or fqns == '') and visibility.include?(pin.visibility)
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
def get_attrs fqns
|
47
|
+
namespace_pins(fqns).select{ |pin| pin.kind == Pin::ATTRIBUTE }
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_superclass fqns
|
51
|
+
fqns_pins(fqns).each do |pin|
|
52
|
+
return pin.superclass_reference.name unless pin.superclass_reference.nil?
|
53
|
+
end
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def get_includes fqns
|
58
|
+
result = []
|
59
|
+
fqns_pins(fqns).each do |pin|
|
60
|
+
result.concat pin.include_references.map(&:name)
|
61
|
+
end
|
62
|
+
result
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_extends fqns
|
66
|
+
result = []
|
67
|
+
fqns_pins(fqns).each do |pin|
|
68
|
+
result.concat pin.extend_references.map(&:name)
|
69
|
+
end
|
70
|
+
result
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_path_pins path
|
74
|
+
base = path.sub(/(#|\.|::)[a-z0-9_]*(\?|\!)?$/i, '')
|
75
|
+
base = '' if base == path
|
76
|
+
namespace_pins(base).select{ |pin| pin.path == path }
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_instance_variables(fqns, scope = :instance)
|
80
|
+
namespace_pins(fqns).select{|pin| pin.kind == Pin::INSTANCE_VARIABLE and pin.scope == scope}
|
81
|
+
end
|
82
|
+
|
83
|
+
def get_symbols
|
84
|
+
symbols.uniq(&:name)
|
85
|
+
end
|
86
|
+
|
87
|
+
def namespace_exists?(fqns)
|
88
|
+
fqns_pins(fqns).any?
|
89
|
+
end
|
90
|
+
|
91
|
+
def namespaces
|
92
|
+
@namespaces ||= Set.new
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def fqns_pins fqns
|
98
|
+
# @todo We probably want to ignore '' namespace here
|
99
|
+
return [] if fqns.nil? #or fqns.empty?
|
100
|
+
if fqns.include?('::')
|
101
|
+
parts = fqns.split('::')
|
102
|
+
name = parts.pop
|
103
|
+
base = parts.join('::')
|
104
|
+
else
|
105
|
+
base = ''
|
106
|
+
name = fqns
|
107
|
+
end
|
108
|
+
namespace_pins(base).select{|pin| pin.name == name and pin.kind == Pin::NAMESPACE}
|
109
|
+
end
|
110
|
+
|
111
|
+
def symbols
|
112
|
+
@symbols ||= []
|
113
|
+
end
|
114
|
+
|
115
|
+
def namespace_pins name
|
116
|
+
namespace_map[name] || []
|
117
|
+
end
|
118
|
+
|
119
|
+
def namespace_map
|
120
|
+
@namespace_map ||= {}
|
121
|
+
end
|
122
|
+
|
123
|
+
def index
|
124
|
+
namespace_map.clear
|
125
|
+
namespaces.clear
|
126
|
+
namespace_map[''] = []
|
127
|
+
pins.each do |pin|
|
128
|
+
namespace_map[pin.namespace] ||= []
|
129
|
+
namespace_map[pin.namespace].push pin
|
130
|
+
namespaces.add pin.path if pin.kind == Pin::NAMESPACE and !pin.path.empty?
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|