solargraph 0.18.3 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
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