solargraph 0.26.1 → 0.27.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/lib/solargraph.rb +5 -2
  3. data/lib/solargraph/api_map.rb +236 -234
  4. data/lib/solargraph/api_map/store.rb +18 -53
  5. data/lib/solargraph/bundle.rb +22 -0
  6. data/lib/solargraph/complex_type.rb +9 -5
  7. data/lib/solargraph/complex_type/type_methods.rb +113 -0
  8. data/lib/solargraph/complex_type/unique_type.rb +35 -0
  9. data/lib/solargraph/core_fills.rb +1 -0
  10. data/lib/solargraph/diagnostics.rb +6 -4
  11. data/lib/solargraph/diagnostics/base.rb +3 -0
  12. data/lib/solargraph/diagnostics/require_not_found.rb +2 -1
  13. data/lib/solargraph/diagnostics/rubocop.rb +21 -6
  14. data/lib/solargraph/diagnostics/type_not_defined.rb +4 -3
  15. data/lib/solargraph/diagnostics/update_errors.rb +18 -0
  16. data/lib/solargraph/language_server/host.rb +90 -222
  17. data/lib/solargraph/language_server/host/cataloger.rb +68 -0
  18. data/lib/solargraph/language_server/host/diagnoser.rb +85 -0
  19. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +35 -24
  20. data/lib/solargraph/language_server/message/text_document/completion.rb +6 -8
  21. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +1 -1
  22. data/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +0 -1
  23. data/lib/solargraph/language_server/transport/socket.rb +4 -6
  24. data/lib/solargraph/language_server/transport/stdio.rb +4 -6
  25. data/lib/solargraph/library.rb +152 -99
  26. data/lib/solargraph/live_map.rb +1 -1
  27. data/lib/solargraph/location.rb +28 -0
  28. data/lib/solargraph/pin.rb +2 -0
  29. data/lib/solargraph/pin/attribute.rb +26 -12
  30. data/lib/solargraph/pin/base.rb +15 -35
  31. data/lib/solargraph/pin/base_variable.rb +7 -15
  32. data/lib/solargraph/pin/block.rb +5 -9
  33. data/lib/solargraph/pin/block_parameter.rb +9 -7
  34. data/lib/solargraph/pin/conversions.rb +5 -5
  35. data/lib/solargraph/pin/duck_method.rb +1 -1
  36. data/lib/solargraph/pin/instance_variable.rb +0 -4
  37. data/lib/solargraph/pin/keyword.rb +4 -0
  38. data/lib/solargraph/pin/localized.rb +5 -3
  39. data/lib/solargraph/pin/method.rb +11 -0
  40. data/lib/solargraph/pin/namespace.rb +7 -3
  41. data/lib/solargraph/pin/proxy_type.rb +3 -7
  42. data/lib/solargraph/pin/reference.rb +2 -2
  43. data/lib/solargraph/pin/symbol.rb +1 -1
  44. data/lib/solargraph/pin/yard_pin/method.rb +2 -2
  45. data/lib/solargraph/pin/yard_pin/namespace.rb +16 -7
  46. data/lib/solargraph/position.rb +103 -0
  47. data/lib/solargraph/range.rb +70 -0
  48. data/lib/solargraph/source.rb +159 -328
  49. data/lib/solargraph/source/chain.rb +38 -55
  50. data/lib/solargraph/source/chain/call.rb +47 -29
  51. data/lib/solargraph/source/chain/class_variable.rb +2 -2
  52. data/lib/solargraph/source/chain/constant.rb +3 -3
  53. data/lib/solargraph/source/chain/definition.rb +7 -3
  54. data/lib/solargraph/source/chain/global_variable.rb +1 -1
  55. data/lib/solargraph/source/chain/head.rb +22 -9
  56. data/lib/solargraph/source/chain/instance_variable.rb +2 -2
  57. data/lib/solargraph/source/chain/link.rb +4 -4
  58. data/lib/solargraph/source/chain/literal.rb +1 -1
  59. data/lib/solargraph/source/chain/variable.rb +2 -2
  60. data/lib/solargraph/source/change.rb +0 -6
  61. data/lib/solargraph/source/cursor.rb +161 -0
  62. data/lib/solargraph/source/encoding_fixes.rb +1 -1
  63. data/lib/solargraph/source/node_chainer.rb +28 -21
  64. data/lib/solargraph/source/node_methods.rb +1 -1
  65. data/lib/solargraph/source/source_chainer.rb +217 -0
  66. data/lib/solargraph/source_map.rb +138 -0
  67. data/lib/solargraph/source_map/clip.rb +123 -0
  68. data/lib/solargraph/{source → source_map}/completion.rb +3 -3
  69. data/lib/solargraph/{source → source_map}/mapper.rb +143 -41
  70. data/lib/solargraph/version.rb +1 -1
  71. data/lib/solargraph/workspace.rb +13 -20
  72. data/lib/solargraph/yard_map.rb +77 -48
  73. metadata +17 -11
  74. data/lib/solargraph/basic_type.rb +0 -33
  75. data/lib/solargraph/basic_type_methods.rb +0 -111
  76. data/lib/solargraph/source/call_chainer.rb +0 -273
  77. data/lib/solargraph/source/fragment.rb +0 -342
  78. data/lib/solargraph/source/location.rb +0 -23
  79. data/lib/solargraph/source/position.rb +0 -95
  80. data/lib/solargraph/source/range.rb +0 -64
@@ -1,273 +0,0 @@
1
- module Solargraph
2
- class Source
3
- # Information about a location in a source, including the location's word
4
- # and signature, literal values at the base of signatures, and whether the
5
- # location is inside a string or comment. ApiMaps use Fragments to provide
6
- # results for completion and definition queries.
7
- #
8
- class CallChainer
9
- include NodeMethods
10
-
11
- private_class_method :new
12
-
13
- class << self
14
- # @param source [Source]
15
- # @param line [Integer]
16
- # @param column [Integer]
17
- # @return [Source::Chain]
18
- def chain source, line, column
19
- new(source, line, column).chain
20
- end
21
- end
22
-
23
- # @param source [Solargraph::Source]
24
- # @param line [Integer]
25
- # @param column [Integer]
26
- def initialize source, line, column
27
- @source = source
28
- # @source.code = source.code
29
- @line = line
30
- @column = column
31
- @calculated_literal = false
32
- end
33
-
34
- # @return [Source::Chain]
35
- def chain
36
- links = []
37
- # @todo Smelly colon handling
38
- if @source.code[0..offset-1].end_with?(':') and !@source.code[0..offset-1].end_with?('::')
39
- links.push Chain::Link.new
40
- links.push Chain::Link.new
41
- elsif @source.string_at?(line, column)
42
- links.push Chain::Literal.new('String')
43
- else
44
- links.push Chain::Literal.new(base_literal) if base_literal?
45
- sig = whole_signature
46
- unless sig.empty?
47
- sig = sig[1..-1] if sig.start_with?('.')
48
- head = true
49
- sig.split('.', -1).each do |word|
50
- if word.include?('::')
51
- # @todo Smelly way of handling constants
52
- parts = (word.start_with?('::') ? word[2..-1] : word).split('::', -1)
53
- last = parts.pop
54
- links.push Chain::Constant.new(parts.join('::')) unless parts.empty?
55
- links.push (last.nil? ? Chain::UNDEFINED_CONSTANT : Chain::Constant.new(last))
56
- else
57
- links.push word_to_link(word, head)
58
- end
59
- head = false
60
- end
61
- end
62
- # Literal string hack
63
- links.push Chain::UNDEFINED_CALL if base_literal? and @source.code[offset - 1] == '.' and links.length == 1
64
- end
65
- @chain ||= Chain.new(source.filename, links)
66
- end
67
-
68
- private
69
-
70
- # The zero-based line number of the fragment's location.
71
- #
72
- # @return [Integer]
73
- attr_reader :line
74
-
75
- # The zero-based column number of the fragment's location.
76
- #
77
- # @return [Integer]
78
- attr_reader :column
79
-
80
- # @return [Solargraph::Source]
81
- attr_reader :source
82
-
83
- def word_to_link word, head
84
- if word.start_with?('@@')
85
- Chain::ClassVariable.new(word)
86
- elsif word.start_with?('@')
87
- Chain::InstanceVariable.new(word)
88
- elsif word.start_with?('$')
89
- Chain::GlobalVariable.new(word)
90
- elsif word.end_with?(':')
91
- Chain::Link.new
92
- elsif word.empty?
93
- Chain::UNDEFINED_CALL
94
- elsif head and !@source.code[signature_data[0]..-1].match(/^[\s]*?#{word}[\s]*?\(/)
95
- # The head needs to allow for ambiguous references to constants and
96
- # methods. For example, `String` might be either. If the word is not
97
- # followed by an open parenthesis, use Chain::Head for ambiguous
98
- # results.
99
- Chain::Head.new(word)
100
- else
101
- Chain::Call.new(word)
102
- end
103
- end
104
-
105
- # An alias for #column.
106
- #
107
- # @return [Integer]
108
- def character
109
- @column
110
- end
111
-
112
- # @return [Source::Position]
113
- def position
114
- @position ||= Position.new(line, column)
115
- end
116
-
117
- # Get the signature up to the current offset. Given the text `foo.bar`,
118
- # the signature at offset 5 is `foo.b`.
119
- #
120
- # @return [String]
121
- def signature
122
- @signature ||= signature_data[1].to_s
123
- end
124
-
125
- # Get the remainder of the word after the current offset. Given the text
126
- # `foobar` with an offset of 3, the remainder is `bar`.
127
- #
128
- # @return [String]
129
- def remainder
130
- @remainder ||= remainder_at(offset)
131
- end
132
-
133
- # Get the whole signature at the current offset, including the final
134
- # word and its remainder.
135
- #
136
- # @return [String]
137
- def whole_signature
138
- @whole_signature ||= signature + remainder
139
- end
140
-
141
- # True if the current offset is inside a string.
142
- #
143
- # @return [Boolean]
144
- def string?
145
- # @string ||= (node.type == :str or node.type == :dstr)
146
- @string ||= @source.string_at?(line, character)
147
- end
148
-
149
- # True if the fragment is a signature that stems from a literal value.
150
- #
151
- # @return [Boolean]
152
- def base_literal?
153
- !base_literal.nil?
154
- end
155
-
156
- # The type of literal value at the root of the signature (or nil).
157
- #
158
- # @return [String]
159
- def base_literal
160
- if @base_literal.nil? and !@calculated_literal
161
- @calculated_literal = true
162
- if signature.start_with?('.')
163
- pn = @source.node_at(line, column - 2)
164
- @base_literal = infer_literal_node_type(pn) unless pn.nil?
165
- end
166
- end
167
- @base_literal
168
- end
169
-
170
- # @return [Integer]
171
- def offset
172
- @offset ||= get_offset(line, column)
173
- end
174
-
175
- def get_offset line, column
176
- Position.line_char_to_offset(@source.code, line, column)
177
- end
178
-
179
- def signature_data
180
- @signature_data ||= get_signature_data_at(offset)
181
- end
182
-
183
- def get_signature_data_at index
184
- brackets = 0
185
- squares = 0
186
- parens = 0
187
- signature = ''
188
- index -=1
189
- in_whitespace = false
190
- while index >= 0
191
- pos = Position.from_offset(@source.code, index)
192
- break if index > 0 and @source.comment_at?(pos.line, pos.character)
193
- unless !in_whitespace and string?
194
- break if brackets > 0 or parens > 0 or squares > 0
195
- char = @source.code[index, 1]
196
- break if char.nil? # @todo Is this the right way to handle this?
197
- if brackets.zero? and parens.zero? and squares.zero? and [' ', "\r", "\n", "\t"].include?(char)
198
- in_whitespace = true
199
- else
200
- if brackets.zero? and parens.zero? and squares.zero? and in_whitespace
201
- unless char == '.' or @source.code[index+1..-1].strip.start_with?('.')
202
- old = @source.code[index+1..-1]
203
- nxt = @source.code[index+1..-1].lstrip
204
- index += (@source.code[index+1..-1].length - @source.code[index+1..-1].lstrip.length)
205
- break
206
- end
207
- end
208
- if char == ')'
209
- parens -=1
210
- elsif char == ']'
211
- squares -=1
212
- elsif char == '}'
213
- brackets -= 1
214
- elsif char == '('
215
- parens += 1
216
- elsif char == '{'
217
- brackets += 1
218
- elsif char == '['
219
- squares += 1
220
- signature = ".[]#{signature}" if parens.zero? and brackets.zero? and squares.zero? and @source.code[index-2] != '%'
221
- end
222
- if brackets.zero? and parens.zero? and squares.zero?
223
- break if ['"', "'", ',', ';', '%'].include?(char)
224
- signature = char + signature if char.match(/[a-z0-9:\._@\$\?\!]/i) and @source.code[index - 1] != '%'
225
- break if char == '$'
226
- if char == '@'
227
- signature = "@#{signature}" if @source.code[index-1, 1] == '@'
228
- break
229
- end
230
- end
231
- in_whitespace = false
232
- end
233
- end
234
- index -= 1
235
- end
236
- # @todo Smelly exceptional case for integer literals
237
- match = signature.match(/^[0-9]+/)
238
- if match
239
- index += match[0].length
240
- signature = signature[match[0].length..-1].to_s
241
- @base_literal = 'Integer'
242
- # @todo Smelly exceptional case for array literals
243
- elsif signature.start_with?('.[]')
244
- index += 2
245
- signature = signature[3..-1].to_s
246
- @base_literal = 'Array'
247
- elsif signature.start_with?('.')
248
- pos = Position.from_offset(source.code, index)
249
- node = source.node_at(pos.line, pos.character)
250
- lit = infer_literal_node_type(node)
251
- unless lit.nil?
252
- signature = signature[1..-1].to_s
253
- index += 1
254
- @base_literal = lit
255
- end
256
- end
257
- [index + 1, signature]
258
- end
259
-
260
- # @return [String]
261
- def remainder_at index
262
- cursor = index
263
- while cursor < @source.code.length
264
- char = @source.code[cursor, 1]
265
- break if char.nil? or char == ''
266
- break unless char.match(/[a-z0-9_\?\!]/i)
267
- cursor += 1
268
- end
269
- @source.code[index..cursor-1].to_s
270
- end
271
- end
272
- end
273
- end
@@ -1,342 +0,0 @@
1
- module Solargraph
2
- class Source
3
- # Information about a location in a source, including the location's word
4
- # and signature, literal values at the base of signatures, and whether the
5
- # location is inside a string or comment. ApiMaps use Fragments to provide
6
- # results for completion and definition queries.
7
- #
8
- class Fragment
9
- include NodeMethods
10
-
11
- # The zero-based line number of the fragment's location.
12
- #
13
- # @return [Integer]
14
- attr_reader :line
15
-
16
- # The zero-based column number of the fragment's location.
17
- #
18
- # @return [Integer]
19
- attr_reader :column
20
-
21
- # @return [Solargraph::Source]
22
- attr_reader :source
23
-
24
- # @param source [Solargraph::Source]
25
- # @param line [Integer]
26
- # @param column [Integer]
27
- def initialize source, line, column
28
- @source = source
29
- @code = source.code
30
- @line = line
31
- @column = column
32
- @calculated_literal = false
33
- end
34
-
35
- # An alias for #column.
36
- #
37
- # @return [Integer]
38
- def character
39
- @column
40
- end
41
-
42
- # @return [Source::Position]
43
- def position
44
- @position ||= Position.new(line, column)
45
- end
46
-
47
- # Get the fully qualified namespace at the current offset.
48
- #
49
- # @return [String]
50
- def namespace
51
- if @namespace.nil?
52
- pin = @source.locate_namespace_pin(line, character)
53
- @namespace = (pin.nil? ? '' : pin.path)
54
- end
55
- @namespace
56
- end
57
-
58
- # True if the fragment is inside a method argument.
59
- #
60
- # @return [Boolean]
61
- def argument?
62
- @argument ||= !signature_position.nil?
63
- end
64
-
65
- # @return [Fragment]
66
- def recipient
67
- return nil if signature_position.nil?
68
- @recipient ||= @source.fragment_at(*signature_position)
69
- end
70
-
71
- # Get the scope at the current offset.
72
- #
73
- # @return [Symbol] :class or :instance
74
- def scope
75
- if @scope.nil?
76
- @scope = :class
77
- @scope = :instance if named_path.kind == Pin::METHOD and named_path.scope == :instance
78
- end
79
- @scope
80
- end
81
-
82
- # Get the signature before the current word. Given the signature
83
- # `String.new.split`, the base is `String.new`.
84
- #
85
- # @return [String]
86
- def base
87
- chain.links[0..-2].map(&:word).join('.')
88
- end
89
-
90
- # @return [Source::Chain]
91
- def chain
92
- @chain ||= generate_chain
93
- end
94
-
95
- # Get the whole signature at the current offset, including the final
96
- # word and its remainder.
97
- #
98
- # @return [String]
99
- def whole_signature
100
- chain.links.reject{|l| l.word == '<undefined>'}.map(&:word).join('.')
101
- end
102
-
103
- # Get the word before the current offset. Given the text `foo.bar`, the
104
- # word at offset 6 is `ba`.
105
- #
106
- # @return [String]
107
- def start_of_word
108
- @start_of_word ||= begin
109
- match = @code[0..offset-1].to_s.match(start_word_pattern)
110
- result = (match ? match[0] : '')
111
- result = ":#{result}" if @code[0..offset-result.length].end_with?('::') and !@code[0..offset-result.length].end_with?('::')
112
- result
113
- end
114
- end
115
-
116
- def word
117
- start_of_word
118
- end
119
-
120
- def end_of_word
121
- @end_of_word ||= begin
122
- match = @code[offset..-1].to_s.match(end_word_pattern)
123
- match ? match[0] : ''
124
- end
125
- end
126
-
127
- def remainder
128
- end_of_word
129
- end
130
-
131
- def whole_word
132
- start_of_word + end_of_word
133
- end
134
-
135
- # True if the current offset is inside a string.
136
- #
137
- # @return [Boolean]
138
- def string?
139
- @string ||= @source.string_at?(line, character)
140
- end
141
-
142
- # True if the current offset is inside a comment.
143
- #
144
- # @return [Boolean]
145
- def comment?
146
- @comment ||= @source.comment_at?(line, column)
147
- end
148
-
149
- # Get the range of the whole word at the current offset, including its
150
- # remainder.
151
- #
152
- # @return [Range]
153
- def word_range
154
- @word_range ||= word_range_at(offset - start_of_word.length, offset + end_of_word.length)
155
- end
156
-
157
- # @return [Solargraph::Pin::Base]
158
- def block
159
- @block ||= @source.locate_block_pin(line, character)
160
- end
161
-
162
- # @return [Solargraph::Pin::Base]
163
- def named_path
164
- @named_path ||= @source.locate_named_path_pin(line, character)
165
- end
166
-
167
- # Get an array of all the locals that are visible from the fragment's
168
- # position. Locals can be local variables, method parameters, or block
169
- # parameters. The array starts with the nearest local pin.
170
- #
171
- # @return [Array<Solargraph::Pin::Base>]
172
- def locals
173
- @locals ||= @source.locals.select{|pin| pin.visible_from?(block, position)}.reverse
174
- end
175
-
176
- # True if the fragment is inside a literal value.
177
- #
178
- # @return [Boolean]
179
- def literal?
180
- !literal.nil?
181
- end
182
-
183
- # The fragment's literal type, or nil if the fragment is not inside a
184
- # literal value.
185
- #
186
- # @return [String]
187
- def literal
188
- if @literal.nil? and !@calculated_actual_literal
189
- @calculated_actual_literal = true
190
- pn = @source.node_at(line, column)
191
- @literal = infer_literal_node_type(pn)
192
- end
193
- end
194
-
195
- # Get a set of available completions for the specified fragment. The
196
- # resulting Completion object contains an array of pins and the range of
197
- # text to replace in the source.
198
- #
199
- # @param api_map [ApiMap]
200
- # @return [Completion]
201
- def complete api_map
202
- return Completion.new([], word_range) if chain.literal? or comment?
203
- result = []
204
- type = infer_base_type(api_map)
205
- if chain.tail.constant?
206
- result.concat api_map.get_constants(type.namespace, namespace)
207
- else
208
- result.concat api_map.get_complex_type_methods(type, namespace, chain.links.length == 1)
209
- if chain.links.length == 1
210
- if word.start_with?('@@')
211
- return package_completions(api_map.get_class_variable_pins(namespace))
212
- elsif word.start_with?('@')
213
- return package_completions(api_map.get_instance_variable_pins(namespace, scope))
214
- elsif word.start_with?('$')
215
- return package_completions(api_map.get_global_variable_pins)
216
- elsif word.start_with?(':') and !word.start_with?('::')
217
- return package_completions(api_map.get_symbols)
218
- end
219
- result.concat api_map.get_constants('', namespace)
220
- result.concat prefer_non_nil_variables(locals)
221
- result.concat api_map.get_methods(namespace, scope: scope, visibility: [:public, :private, :protected])
222
- result.concat api_map.get_methods('Kernel')
223
- result.concat ApiMap.keywords
224
- end
225
- end
226
- package_completions(result)
227
- end
228
-
229
- def define api_map
230
- return [] if chain.literal?
231
- return [] if string? or comment? or literal?
232
- # HACK: Checking for self first because it's also a keyword
233
- return [] if ApiMap::KEYWORDS.include?(chain.links.first.word) and chain.links.first.word != 'self'
234
- chain.define_with(api_map, named_path, locals)
235
- end
236
-
237
- # Get an array of pins that describe the method being called by the
238
- # argument list where the fragment is located. This is useful for queries
239
- # that need to know what parameters the current method expects to receive.
240
- #
241
- # If the fragment is not inside an argument list, return an empty array.
242
- #
243
- # @param api_map [Solargraph::Source::Fragment]
244
- # @return [Array<Solargraph::Pin::Base>]
245
- def signify api_map
246
- return [] unless argument?
247
- return [] if recipient.whole_signature.nil? or recipient.whole_signature.empty?
248
- result = []
249
- result.concat recipient.define(api_map)
250
- result.select{ |pin| pin.kind == Pin::METHOD }
251
- end
252
-
253
- # @param api_map [ApiMap]
254
- # @return [ComplexType]
255
- def infer_base_type api_map
256
- chain.infer_base_type_with(api_map, named_path, locals)
257
- end
258
-
259
- private
260
-
261
- # @return [Integer]
262
- def offset
263
- @offset ||= get_offset(line, column)
264
- end
265
-
266
- def get_offset line, column
267
- Position.line_char_to_offset(@code, line, column)
268
- end
269
-
270
- def get_position_at(offset)
271
- pos = Position.from_offset(@code, offset)
272
- [pos.line, pos.character]
273
- end
274
-
275
- # @return Solargraph::Source::Range
276
- def word_range_at first, last
277
- s = Position.from_offset(@source.code, first)
278
- e = Position.from_offset(@source.code, last)
279
- Solargraph::Source::Range.new(s, e)
280
- end
281
-
282
- def signature_position
283
- if @signature_position.nil?
284
- open_parens = 0
285
- cursor = offset - 1
286
- while cursor >= 0
287
- break if cursor < 0
288
- if @code[cursor] == ')'
289
- open_parens -= 1
290
- elsif @code[cursor] == '('
291
- open_parens += 1
292
- end
293
- break if open_parens == 1
294
- cursor -= 1
295
- end
296
- if cursor >= 0
297
- @signature_position = get_position_at(cursor)
298
- end
299
- end
300
- @signature_position
301
- end
302
-
303
- def generate_chain
304
- CallChainer.chain(source, line, column)
305
- end
306
-
307
- def start_word_pattern
308
- /(@{1,2}|\$)?([a-z0-9_]|[^\u0000-\u007F])*\z/i
309
- end
310
-
311
- def end_word_pattern
312
- /^([a-z0-9_]|[^\u0000-\u007F])*[\?\!]?/i
313
- end
314
-
315
- # @param fragment [Source::Fragment]
316
- # @param result [Array<Pin::Base>]
317
- # @return [Completion]
318
- def package_completions result
319
- frag_start = word.to_s.downcase
320
- filtered = result.uniq(&:identifier).select{|s| s.name.downcase.start_with?(frag_start) and (s.kind != Pin::METHOD or s.name.match(/^[a-z0-9_]+(\!|\?|=)?$/i))}.sort_by.with_index{ |x, idx| [x.name, idx] }
321
- Completion.new(filtered, word_range)
322
- end
323
-
324
- # Sort an array of pins to put nil or undefined variables last.
325
- #
326
- # @param pins [Array<Solargraph::Pin::Base>]
327
- # @return [Array<Solargraph::Pin::Base>]
328
- def prefer_non_nil_variables pins
329
- result = []
330
- nil_pins = []
331
- pins.each do |pin|
332
- if pin.variable? and pin.nil_assignment?
333
- nil_pins.push pin
334
- else
335
- result.push pin
336
- end
337
- end
338
- result + nil_pins
339
- end
340
- end
341
- end
342
- end