solargraph 0.18.3 → 0.19.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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/lib/solargraph/api_map/probe.rb +222 -0
  3. data/lib/solargraph/api_map/source_to_yard.rb +3 -3
  4. data/lib/solargraph/api_map/store.rb +135 -0
  5. data/lib/solargraph/api_map.rb +169 -609
  6. data/lib/solargraph/diagnostics/rubocop.rb +4 -4
  7. data/lib/solargraph/language_server/host.rb +53 -19
  8. data/lib/solargraph/language_server/message/extended/document.rb +1 -1
  9. data/lib/solargraph/language_server/message/extended/search.rb +1 -1
  10. data/lib/solargraph/language_server/message/method_not_found.rb +1 -1
  11. data/lib/solargraph/language_server/message/text_document/definition.rb +2 -15
  12. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +2 -15
  13. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +3 -15
  14. data/lib/solargraph/language_server/message_types.rb +10 -0
  15. data/lib/solargraph/language_server.rb +1 -0
  16. data/lib/solargraph/library.rb +8 -0
  17. data/lib/solargraph/node_methods.rb +6 -1
  18. data/lib/solargraph/page.rb +2 -1
  19. data/lib/solargraph/pin/attribute.rb +8 -12
  20. data/lib/solargraph/pin/base.rb +20 -95
  21. data/lib/solargraph/pin/base_variable.rb +15 -74
  22. data/lib/solargraph/pin/block.rb +21 -0
  23. data/lib/solargraph/pin/block_parameter.rb +30 -44
  24. data/lib/solargraph/pin/class_variable.rb +3 -0
  25. data/lib/solargraph/pin/constant.rb +4 -8
  26. data/lib/solargraph/pin/conversions.rb +4 -3
  27. data/lib/solargraph/pin/documenting.rb +27 -0
  28. data/lib/solargraph/pin/global_variable.rb +3 -0
  29. data/lib/solargraph/pin/instance_variable.rb +5 -4
  30. data/lib/solargraph/pin/local_variable.rb +8 -15
  31. data/lib/solargraph/pin/localized.rb +12 -0
  32. data/lib/solargraph/pin/method.rb +6 -67
  33. data/lib/solargraph/pin/method_parameter.rb +24 -11
  34. data/lib/solargraph/pin/namespace.rb +26 -35
  35. data/lib/solargraph/pin/reference.rb +15 -8
  36. data/lib/solargraph/pin/symbol.rb +34 -3
  37. data/lib/solargraph/pin/yard_object.rb +11 -4
  38. data/lib/solargraph/pin.rb +16 -2
  39. data/lib/solargraph/server.rb +2 -2
  40. data/lib/solargraph/source/change.rb +10 -13
  41. data/lib/solargraph/source/fragment.rb +42 -94
  42. data/lib/solargraph/source/location.rb +13 -0
  43. data/lib/solargraph/source/mapper.rb +426 -0
  44. data/lib/solargraph/source/position.rb +1 -0
  45. data/lib/solargraph/source/range.rb +11 -3
  46. data/lib/solargraph/source.rb +93 -284
  47. data/lib/solargraph/version.rb +1 -1
  48. data/lib/solargraph/views/_method.erb +59 -60
  49. data/lib/solargraph/views/_name_type_tag.erb +10 -0
  50. data/lib/solargraph/views/_namespace.erb +26 -26
  51. data/lib/solargraph/views/document.erb +23 -16
  52. data/lib/solargraph/views/layout.erb +38 -10
  53. data/lib/solargraph/views/search.erb +12 -11
  54. data/lib/solargraph/workspace/config.rb +27 -6
  55. data/lib/solargraph/workspace.rb +10 -2
  56. data/lib/solargraph.rb +10 -2
  57. data/lib/yard-solargraph.rb +3 -0
  58. metadata +25 -20
  59. data/lib/solargraph/pin/directed/attribute.rb +0 -20
  60. data/lib/solargraph/pin/directed/method.rb +0 -22
  61. data/lib/solargraph/pin/directed.rb +0 -9
  62. data/lib/solargraph/pin/parameter.rb +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 249d94c36b0d3d32bd9c7d06933aa5b886b3c73e
4
- data.tar.gz: 7229efdede3aee37cca5da7ed160f378c5cee95e
3
+ metadata.gz: 7584a4d20d5562279e2e86887fe56a448776ba91
4
+ data.tar.gz: bd353cd7bf5140f80f0a53155bfc03cb0dc1d73b
5
5
  SHA512:
6
- metadata.gz: d46fb7bbec935080b31fe33d9219c5189c0a5d475e4be2329bed3ac502f0a195b6c320438470289aaacc643086f213a472cd38408e168ce20fc29b33225593d2
7
- data.tar.gz: 5023b4965a27c1e60f52537ee8a2cc72eafbde90eab78f7bbbd3d71dff412034d86f4f53b638d74a7542357024e8d12943b71b1797e02de10573922a03178e05
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.source.filename
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.source.filename
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.source.filename
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