solargraph 0.54.0 → 0.55.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +58 -0
- data/lib/solargraph/api_map/cache.rb +10 -1
- data/lib/solargraph/api_map/index.rb +167 -0
- data/lib/solargraph/api_map/store.rb +75 -122
- data/lib/solargraph/api_map.rb +109 -41
- data/lib/solargraph/bench.rb +17 -1
- data/lib/solargraph/complex_type/type_methods.rb +17 -11
- data/lib/solargraph/complex_type/unique_type.rb +181 -17
- data/lib/solargraph/complex_type.rb +103 -24
- data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +51 -0
- data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +100 -0
- data/lib/solargraph/convention/struct_definition.rb +101 -0
- data/lib/solargraph/convention.rb +2 -0
- data/lib/solargraph/doc_map.rb +43 -18
- data/lib/solargraph/equality.rb +33 -0
- data/lib/solargraph/language_server/host/message_worker.rb +51 -5
- data/lib/solargraph/language_server/host.rb +13 -11
- data/lib/solargraph/language_server/message/base.rb +19 -12
- data/lib/solargraph/language_server/message/initialize.rb +3 -1
- data/lib/solargraph/language_server/message/text_document/completion.rb +0 -3
- data/lib/solargraph/language_server/message/text_document/definition.rb +3 -3
- data/lib/solargraph/language_server/message/text_document/document_symbol.rb +3 -3
- data/lib/solargraph/language_server/message/text_document/formatting.rb +4 -0
- data/lib/solargraph/language_server/message/text_document/hover.rb +1 -1
- data/lib/solargraph/language_server/message/text_document/type_definition.rb +3 -3
- data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +2 -2
- data/lib/solargraph/language_server/progress.rb +19 -2
- data/lib/solargraph/library.rb +31 -41
- data/lib/solargraph/location.rb +29 -1
- data/lib/solargraph/parser/comment_ripper.rb +11 -6
- data/lib/solargraph/parser/flow_sensitive_typing.rb +226 -0
- data/lib/solargraph/parser/node_methods.rb +15 -1
- data/lib/solargraph/parser/parser_gem/class_methods.rb +11 -6
- data/lib/solargraph/parser/parser_gem/node_chainer.rb +10 -10
- data/lib/solargraph/parser/parser_gem/node_methods.rb +6 -4
- data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +21 -0
- data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +23 -19
- data/lib/solargraph/parser/parser_gem/node_processors/casgn_node.rb +21 -1
- data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +21 -0
- data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +8 -2
- data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +26 -5
- data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +41 -0
- data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +1 -1
- data/lib/solargraph/parser/parser_gem/node_processors/until_node.rb +28 -0
- data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +28 -0
- data/lib/solargraph/parser/parser_gem/node_processors.rb +10 -0
- data/lib/solargraph/parser.rb +3 -5
- data/lib/solargraph/pin/base.rb +47 -17
- data/lib/solargraph/pin/base_variable.rb +11 -4
- data/lib/solargraph/pin/block.rb +8 -26
- data/lib/solargraph/pin/breakable.rb +9 -0
- data/lib/solargraph/pin/callable.rb +147 -0
- data/lib/solargraph/pin/closure.rb +8 -3
- data/lib/solargraph/pin/common.rb +2 -6
- data/lib/solargraph/pin/conversions.rb +3 -2
- data/lib/solargraph/pin/instance_variable.rb +2 -2
- data/lib/solargraph/pin/local_variable.rb +7 -1
- data/lib/solargraph/pin/method.rb +76 -48
- data/lib/solargraph/pin/namespace.rb +14 -11
- data/lib/solargraph/pin/parameter.rb +24 -17
- data/lib/solargraph/pin/proxy_type.rb +13 -7
- data/lib/solargraph/pin/signature.rb +3 -129
- data/lib/solargraph/pin/until.rb +18 -0
- data/lib/solargraph/pin/while.rb +18 -0
- data/lib/solargraph/pin.rb +7 -1
- data/lib/solargraph/position.rb +7 -0
- data/lib/solargraph/range.rb +9 -4
- data/lib/solargraph/rbs_map/conversions.rb +84 -45
- data/lib/solargraph/rbs_map/core_fills.rb +16 -9
- data/lib/solargraph/rbs_map.rb +1 -0
- data/lib/solargraph/shell.rb +19 -2
- data/lib/solargraph/source/chain/array.rb +10 -8
- data/lib/solargraph/source/chain/block_symbol.rb +1 -1
- data/lib/solargraph/source/chain/block_variable.rb +1 -1
- data/lib/solargraph/source/chain/call.rb +125 -61
- data/lib/solargraph/source/chain/constant.rb +1 -1
- data/lib/solargraph/source/chain/hash.rb +8 -2
- data/lib/solargraph/source/chain/if.rb +5 -0
- data/lib/solargraph/source/chain/link.rb +28 -5
- data/lib/solargraph/source/chain/literal.rb +27 -2
- data/lib/solargraph/source/chain/or.rb +1 -1
- data/lib/solargraph/source/chain/z_super.rb +1 -1
- data/lib/solargraph/source/chain.rb +131 -63
- data/lib/solargraph/source/cursor.rb +3 -2
- data/lib/solargraph/source/source_chainer.rb +2 -2
- data/lib/solargraph/source.rb +104 -86
- data/lib/solargraph/source_map/clip.rb +8 -6
- data/lib/solargraph/source_map/data.rb +30 -0
- data/lib/solargraph/source_map.rb +28 -16
- data/lib/solargraph/type_checker/checks.rb +4 -0
- data/lib/solargraph/type_checker/rules.rb +6 -1
- data/lib/solargraph/type_checker.rb +48 -21
- data/lib/solargraph/version.rb +1 -1
- data/lib/solargraph/views/environment.erb +3 -5
- data/lib/solargraph/workspace/config.rb +7 -3
- data/lib/solargraph/workspace.rb +1 -1
- data/lib/solargraph/yard_map/mapper/to_constant.rb +1 -0
- data/lib/solargraph/yard_map/mapper/to_method.rb +42 -15
- data/lib/solargraph/yard_map/mapper/to_namespace.rb +1 -0
- data/lib/solargraph/yard_map/mapper.rb +1 -0
- data/lib/solargraph/yardoc.rb +1 -1
- data/lib/solargraph.rb +1 -0
- data/solargraph.gemspec +5 -5
- metadata +41 -25
data/lib/solargraph/library.rb
CHANGED
|
@@ -27,11 +27,11 @@ module Solargraph
|
|
|
27
27
|
def initialize workspace = Solargraph::Workspace.new, name = nil
|
|
28
28
|
@workspace = workspace
|
|
29
29
|
@name = name
|
|
30
|
-
@threads = []
|
|
31
30
|
# @type [Integer, nil]
|
|
32
31
|
@total = nil
|
|
33
32
|
# @type [Source, nil]
|
|
34
33
|
@current = nil
|
|
34
|
+
@sync_count = 0
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def inspect
|
|
@@ -44,7 +44,7 @@ module Solargraph
|
|
|
44
44
|
#
|
|
45
45
|
# @return [Boolean]
|
|
46
46
|
def synchronized?
|
|
47
|
-
|
|
47
|
+
@sync_count < 2
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
# Attach a source to the library.
|
|
@@ -132,6 +132,7 @@ module Solargraph
|
|
|
132
132
|
result = false
|
|
133
133
|
filenames.each do |filename|
|
|
134
134
|
detach filename
|
|
135
|
+
source_map_hash.delete(filename)
|
|
135
136
|
result ||= workspace.remove(filename)
|
|
136
137
|
end
|
|
137
138
|
result
|
|
@@ -174,9 +175,9 @@ module Solargraph
|
|
|
174
175
|
# @return [Array<Solargraph::Pin::Base>, nil]
|
|
175
176
|
# @todo Take filename/position instead of filename/line/column
|
|
176
177
|
def definitions_at filename, line, column
|
|
178
|
+
sync_catalog
|
|
177
179
|
position = Position.new(line, column)
|
|
178
180
|
cursor = Source::Cursor.new(read(filename), position)
|
|
179
|
-
sync_catalog
|
|
180
181
|
if cursor.comment?
|
|
181
182
|
source = read(filename)
|
|
182
183
|
offset = Solargraph::Position.to_offset(source.code, Solargraph::Position.new(line, column))
|
|
@@ -190,7 +191,10 @@ module Solargraph
|
|
|
190
191
|
[]
|
|
191
192
|
end
|
|
192
193
|
else
|
|
193
|
-
mutex.synchronize
|
|
194
|
+
mutex.synchronize do
|
|
195
|
+
clip = api_map.clip(cursor)
|
|
196
|
+
clip.define.map { |pin| pin.realize(api_map) }
|
|
197
|
+
end
|
|
194
198
|
end
|
|
195
199
|
rescue FileNotFoundError => e
|
|
196
200
|
handle_file_not_found(filename, e)
|
|
@@ -205,9 +209,9 @@ module Solargraph
|
|
|
205
209
|
# @return [Array<Solargraph::Pin::Base>, nil]
|
|
206
210
|
# @todo Take filename/position instead of filename/line/column
|
|
207
211
|
def type_definitions_at filename, line, column
|
|
212
|
+
sync_catalog
|
|
208
213
|
position = Position.new(line, column)
|
|
209
214
|
cursor = Source::Cursor.new(read(filename), position)
|
|
210
|
-
sync_catalog
|
|
211
215
|
mutex.synchronize { api_map.clip(cursor).types }
|
|
212
216
|
rescue FileNotFoundError => e
|
|
213
217
|
handle_file_not_found filename, e
|
|
@@ -222,9 +226,9 @@ module Solargraph
|
|
|
222
226
|
# @return [Array<Solargraph::Pin::Base>]
|
|
223
227
|
# @todo Take filename/position instead of filename/line/column
|
|
224
228
|
def signatures_at filename, line, column
|
|
229
|
+
sync_catalog
|
|
225
230
|
position = Position.new(line, column)
|
|
226
231
|
cursor = Source::Cursor.new(read(filename), position)
|
|
227
|
-
sync_catalog
|
|
228
232
|
mutex.synchronize { api_map.clip(cursor).signify }
|
|
229
233
|
end
|
|
230
234
|
|
|
@@ -233,7 +237,7 @@ module Solargraph
|
|
|
233
237
|
# @param column [Integer]
|
|
234
238
|
# @param strip [Boolean] Strip special characters from variable names
|
|
235
239
|
# @param only [Boolean] Search for references in the current file only
|
|
236
|
-
# @return [Array<Solargraph::
|
|
240
|
+
# @return [Array<Solargraph::Location>]
|
|
237
241
|
# @todo Take a Location instead of filename/line/column
|
|
238
242
|
def references_from filename, line, column, strip: false, only: false
|
|
239
243
|
sync_catalog
|
|
@@ -391,6 +395,8 @@ module Solargraph
|
|
|
391
395
|
return [] unless open?(filename)
|
|
392
396
|
result = []
|
|
393
397
|
source = read(filename)
|
|
398
|
+
|
|
399
|
+
# @type [Hash{Class<Solargraph::Diagnostics::Base> => Array<String>}]
|
|
394
400
|
repargs = {}
|
|
395
401
|
workspace.config.reporters.each do |line|
|
|
396
402
|
if line == 'all!'
|
|
@@ -416,20 +422,7 @@ module Solargraph
|
|
|
416
422
|
#
|
|
417
423
|
# @return [void]
|
|
418
424
|
def catalog
|
|
419
|
-
@
|
|
420
|
-
@threads.push(Thread.new do
|
|
421
|
-
sleep 0.05 if RUBY_PLATFORM =~ /mingw/
|
|
422
|
-
next unless @threads.last == Thread.current
|
|
423
|
-
|
|
424
|
-
mutex.synchronize do
|
|
425
|
-
logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}"
|
|
426
|
-
api_map.catalog bench
|
|
427
|
-
logger.info "Catalog complete (#{api_map.source_maps.length} files, #{api_map.pins.length} pins)"
|
|
428
|
-
logger.info "#{api_map.uncached_gemspecs.length} uncached gemspecs"
|
|
429
|
-
cache_next_gemspec
|
|
430
|
-
end
|
|
431
|
-
end)
|
|
432
|
-
@threads.last.run if RUBY_PLATFORM =~ /mingw/
|
|
425
|
+
@sync_count += 1
|
|
433
426
|
end
|
|
434
427
|
|
|
435
428
|
# @return [Bench]
|
|
@@ -437,7 +430,8 @@ module Solargraph
|
|
|
437
430
|
Bench.new(
|
|
438
431
|
source_maps: source_map_hash.values,
|
|
439
432
|
workspace: workspace,
|
|
440
|
-
external_requires: external_requires
|
|
433
|
+
external_requires: external_requires,
|
|
434
|
+
live_map: @current ? source_map_hash[@current.filename] : nil
|
|
441
435
|
)
|
|
442
436
|
end
|
|
443
437
|
|
|
@@ -467,7 +461,6 @@ module Solargraph
|
|
|
467
461
|
# @param source [Source]
|
|
468
462
|
# @return [Boolean] True if the source was merged into the workspace.
|
|
469
463
|
def merge source
|
|
470
|
-
Logging.logger.debug "Merging source: #{source.filename}"
|
|
471
464
|
result = workspace.merge(source)
|
|
472
465
|
maybe_map source
|
|
473
466
|
result
|
|
@@ -489,7 +482,6 @@ module Solargraph
|
|
|
489
482
|
if src
|
|
490
483
|
Logging.logger.debug "Mapping #{src.filename}"
|
|
491
484
|
source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
|
|
492
|
-
find_external_requires(source_map_hash[src.filename])
|
|
493
485
|
source_map_hash[src.filename]
|
|
494
486
|
else
|
|
495
487
|
false
|
|
@@ -500,7 +492,7 @@ module Solargraph
|
|
|
500
492
|
def map!
|
|
501
493
|
workspace.sources.each do |src|
|
|
502
494
|
source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
|
|
503
|
-
find_external_requires
|
|
495
|
+
find_external_requires source_map_hash[src.filename]
|
|
504
496
|
end
|
|
505
497
|
self
|
|
506
498
|
end
|
|
@@ -580,22 +572,10 @@ module Solargraph
|
|
|
580
572
|
return unless source
|
|
581
573
|
return unless @current == source || workspace.has_file?(source.filename)
|
|
582
574
|
if source_map_hash.key?(source.filename)
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
source.synchronized?
|
|
586
|
-
if source.synchronized?
|
|
587
|
-
new_map = Solargraph::SourceMap.map(source)
|
|
588
|
-
unless source_map_hash[source.filename].try_merge!(new_map)
|
|
589
|
-
source_map_hash[source.filename] = new_map
|
|
590
|
-
find_external_requires(source_map_hash[source.filename])
|
|
591
|
-
end
|
|
592
|
-
else
|
|
593
|
-
# @todo Smelly instance variable access
|
|
594
|
-
source_map_hash[source.filename].instance_variable_set(:@source, source)
|
|
595
|
-
end
|
|
575
|
+
new_map = Solargraph::SourceMap.map(source)
|
|
576
|
+
source_map_hash[source.filename] = new_map
|
|
596
577
|
else
|
|
597
578
|
source_map_hash[source.filename] = Solargraph::SourceMap.map(source)
|
|
598
|
-
find_external_requires(source_map_hash[source.filename])
|
|
599
579
|
end
|
|
600
580
|
end
|
|
601
581
|
|
|
@@ -625,6 +605,7 @@ module Solargraph
|
|
|
625
605
|
ensure
|
|
626
606
|
end_cache_progress
|
|
627
607
|
catalog
|
|
608
|
+
sync_catalog
|
|
628
609
|
end
|
|
629
610
|
end
|
|
630
611
|
|
|
@@ -666,8 +647,17 @@ module Solargraph
|
|
|
666
647
|
end
|
|
667
648
|
|
|
668
649
|
def sync_catalog
|
|
669
|
-
@
|
|
670
|
-
|
|
650
|
+
return if @sync_count == 0
|
|
651
|
+
|
|
652
|
+
mutex.synchronize do
|
|
653
|
+
logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}"
|
|
654
|
+
api_map.catalog bench
|
|
655
|
+
source_map_hash.values.each { |map| find_external_requires(map) }
|
|
656
|
+
logger.info "Catalog complete (#{api_map.source_maps.length} files, #{api_map.pins.length} pins)"
|
|
657
|
+
logger.info "#{api_map.uncached_gemspecs.length} uncached gemspecs"
|
|
658
|
+
cache_next_gemspec
|
|
659
|
+
@sync_count = 0
|
|
660
|
+
end
|
|
671
661
|
end
|
|
672
662
|
end
|
|
673
663
|
end
|
data/lib/solargraph/location.rb
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Solargraph
|
|
4
|
-
# A section of text identified by its filename
|
|
4
|
+
# A pointer to a section of source text identified by its filename
|
|
5
|
+
# and Range.
|
|
5
6
|
#
|
|
6
7
|
class Location
|
|
8
|
+
include Equality
|
|
9
|
+
|
|
7
10
|
# @return [String]
|
|
8
11
|
attr_reader :filename
|
|
9
12
|
|
|
@@ -17,6 +20,24 @@ module Solargraph
|
|
|
17
20
|
@range = range
|
|
18
21
|
end
|
|
19
22
|
|
|
23
|
+
# @sg-ignore Fix "Not enough arguments to Module#protected"
|
|
24
|
+
protected def equality_fields
|
|
25
|
+
[filename, range]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @param location [self]
|
|
29
|
+
def contain? location
|
|
30
|
+
range.contain?(location.range.start) && range.contain?(location.range.ending) && filename == location.filename
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def inspect
|
|
34
|
+
"<#{self.class.name}: filename=#{filename}, range=#{range.inspect}>"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def to_s
|
|
38
|
+
inspect
|
|
39
|
+
end
|
|
40
|
+
|
|
20
41
|
# @return [Hash]
|
|
21
42
|
def to_hash
|
|
22
43
|
{
|
|
@@ -25,6 +46,13 @@ module Solargraph
|
|
|
25
46
|
}
|
|
26
47
|
end
|
|
27
48
|
|
|
49
|
+
# @param node [Parser::AST::Node, nil]
|
|
50
|
+
def self.from_node(node)
|
|
51
|
+
return nil if node.nil? || node.loc.nil?
|
|
52
|
+
range = Range.from_node(node)
|
|
53
|
+
self.new(node.loc.expression.source_buffer.name, range)
|
|
54
|
+
end
|
|
55
|
+
|
|
28
56
|
# @param other [BasicObject]
|
|
29
57
|
def == other
|
|
30
58
|
return false unless other.is_a?(Location)
|
|
@@ -13,6 +13,7 @@ module Solargraph
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def on_comment *args
|
|
16
|
+
# @type [Array(Symbol, String, Array([Integer, nil], [Integer, nil]))]
|
|
16
17
|
result = super
|
|
17
18
|
if @buffer_lines[result[2][0]][0..result[2][1]].strip =~ /^#/
|
|
18
19
|
chomped = result[1].chomp
|
|
@@ -24,24 +25,28 @@ module Solargraph
|
|
|
24
25
|
result
|
|
25
26
|
end
|
|
26
27
|
|
|
28
|
+
# @param result [Array(Symbol, String, Array([Integer, nil], [Integer, nil]))]
|
|
29
|
+
# @return [void]
|
|
30
|
+
def create_snippet(result)
|
|
31
|
+
chomped = result[1].chomp
|
|
32
|
+
@comments[result[2][0]] = Snippet.new(Range.from_to(result[2][0] || 0, result[2][1] || 0, result[2][0] || 0, (result[2][1] || 0) + chomped.length), chomped)
|
|
33
|
+
end
|
|
34
|
+
|
|
27
35
|
def on_embdoc_beg *args
|
|
28
36
|
result = super
|
|
29
|
-
|
|
30
|
-
@comments[result[2][0]] = Snippet.new(Range.from_to(result[2][0], result[2][1], result[2][0], result[2][1] + chomped.length), chomped)
|
|
37
|
+
create_snippet(result)
|
|
31
38
|
result
|
|
32
39
|
end
|
|
33
40
|
|
|
34
41
|
def on_embdoc *args
|
|
35
42
|
result = super
|
|
36
|
-
|
|
37
|
-
@comments[result[2][0]] = Snippet.new(Range.from_to(result[2][0], result[2][1], result[2][0], result[2][1] + chomped.length), chomped)
|
|
43
|
+
create_snippet(result)
|
|
38
44
|
result
|
|
39
45
|
end
|
|
40
46
|
|
|
41
47
|
def on_embdoc_end *args
|
|
42
48
|
result = super
|
|
43
|
-
|
|
44
|
-
@comments[result[2][0]] = Snippet.new(Range.from_to(result[2][0], result[2][1], result[2][0], result[2][1] + chomped.length), chomped)
|
|
49
|
+
create_snippet(result)
|
|
45
50
|
result
|
|
46
51
|
end
|
|
47
52
|
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
module Solargraph
|
|
2
|
+
module Parser
|
|
3
|
+
class FlowSensitiveTyping
|
|
4
|
+
include Solargraph::Parser::NodeMethods
|
|
5
|
+
|
|
6
|
+
# @param locals [Array<Solargraph::Pin::LocalVariable, Solargraph::Pin::Parameter>]
|
|
7
|
+
def initialize(locals, enclosing_breakable_pin = nil)
|
|
8
|
+
@locals = locals
|
|
9
|
+
@enclosing_breakable_pin = enclosing_breakable_pin
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# @param and_node [Parser::AST::Node]
|
|
13
|
+
def process_and(and_node, true_ranges = [])
|
|
14
|
+
lhs = and_node.children[0]
|
|
15
|
+
rhs = and_node.children[1]
|
|
16
|
+
|
|
17
|
+
before_rhs_loc = rhs.location.expression.adjust(begin_pos: -1)
|
|
18
|
+
before_rhs_pos = Position.new(before_rhs_loc.line, before_rhs_loc.column)
|
|
19
|
+
|
|
20
|
+
rhs_presence = Range.new(before_rhs_pos,
|
|
21
|
+
get_node_end_position(rhs))
|
|
22
|
+
process_isa(lhs, true_ranges + [rhs_presence])
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @param if_node [Parser::AST::Node]
|
|
26
|
+
def process_if(if_node)
|
|
27
|
+
#
|
|
28
|
+
# See if we can refine a type based on the result of 'if foo.nil?'
|
|
29
|
+
#
|
|
30
|
+
# [3] pry(main)> require 'parser/current'; Parser::CurrentRuby.parse("if foo.is_a? Baz; then foo; else bar; end")
|
|
31
|
+
# => s(:if,
|
|
32
|
+
# s(:send,
|
|
33
|
+
# s(:send, nil, :foo), :is_a?,
|
|
34
|
+
# s(:const, nil, :Baz)),
|
|
35
|
+
# s(:send, nil, :foo),
|
|
36
|
+
# s(:send, nil, :bar))
|
|
37
|
+
# [4] pry(main)>
|
|
38
|
+
conditional_node = if_node.children[0]
|
|
39
|
+
then_clause = if_node.children[1]
|
|
40
|
+
else_clause = if_node.children[2]
|
|
41
|
+
|
|
42
|
+
true_ranges = []
|
|
43
|
+
if always_breaks?(else_clause)
|
|
44
|
+
unless enclosing_breakable_pin.nil?
|
|
45
|
+
rest_of_breakable_body = Range.new(get_node_end_position(if_node),
|
|
46
|
+
get_node_end_position(enclosing_breakable_pin.node))
|
|
47
|
+
true_ranges << rest_of_breakable_body
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
unless then_clause.nil?
|
|
52
|
+
#
|
|
53
|
+
# Add specialized locals for the then clause range
|
|
54
|
+
#
|
|
55
|
+
before_then_clause_loc = then_clause.location.expression.adjust(begin_pos: -1)
|
|
56
|
+
before_then_clause_pos = Position.new(before_then_clause_loc.line, before_then_clause_loc.column)
|
|
57
|
+
true_ranges << Range.new(before_then_clause_pos,
|
|
58
|
+
get_node_end_position(then_clause))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
process_conditional(conditional_node, true_ranges)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class << self
|
|
65
|
+
include Logging
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Find a variable pin by name and where it is used.
|
|
69
|
+
#
|
|
70
|
+
# Resolves our most specific view of this variable's type by
|
|
71
|
+
# preferring pins created by flow-sensitive typing when we have
|
|
72
|
+
# them based on the Closure and Location.
|
|
73
|
+
#
|
|
74
|
+
# @param pins [Array<Pin::LocalVariable>]
|
|
75
|
+
# @param closure [Pin::Closure]
|
|
76
|
+
# @param location [Location]
|
|
77
|
+
def self.visible_pins(pins, name, closure, location)
|
|
78
|
+
logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location})" }
|
|
79
|
+
pins_with_name = pins.select { |p| p.name == name }
|
|
80
|
+
if pins_with_name.empty?
|
|
81
|
+
logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => [] - no pins with name" }
|
|
82
|
+
return []
|
|
83
|
+
end
|
|
84
|
+
pins_with_specific_visibility = pins.select { |p| p.name == name && p.presence && p.visible_at?(closure, location) }
|
|
85
|
+
if pins_with_specific_visibility.empty?
|
|
86
|
+
logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => #{pins_with_name} - no pins with specific visibility" }
|
|
87
|
+
return pins_with_name
|
|
88
|
+
end
|
|
89
|
+
visible_pins_specific_to_this_closure = pins_with_specific_visibility.select { |p| p.closure == closure }
|
|
90
|
+
if visible_pins_specific_to_this_closure.empty?
|
|
91
|
+
logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => #{pins_with_specific_visibility} - no visible pins specific to this closure (#{closure})}" }
|
|
92
|
+
return pins_with_specific_visibility
|
|
93
|
+
end
|
|
94
|
+
flow_defined_pins = pins_with_specific_visibility.select { |p| p.presence_certain? }
|
|
95
|
+
if flow_defined_pins.empty?
|
|
96
|
+
logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => #{visible_pins_specific_to_this_closure} - no flow-defined pins" }
|
|
97
|
+
return visible_pins_specific_to_this_closure
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => #{flow_defined_pins}" }
|
|
101
|
+
|
|
102
|
+
flow_defined_pins
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
include Logging
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
# @param pin [Pin::LocalVariable]
|
|
110
|
+
# @param if_node [Parser::AST::Node]
|
|
111
|
+
def add_downcast_local(pin, downcast_type_name, presence)
|
|
112
|
+
# @todo Create pin#update method
|
|
113
|
+
new_pin = Solargraph::Pin::LocalVariable.new(
|
|
114
|
+
location: pin.location,
|
|
115
|
+
closure: pin.closure,
|
|
116
|
+
name: pin.name,
|
|
117
|
+
assignment: pin.assignment,
|
|
118
|
+
comments: pin.comments,
|
|
119
|
+
presence: presence,
|
|
120
|
+
return_type: ComplexType.try_parse(downcast_type_name),
|
|
121
|
+
presence_certain: true
|
|
122
|
+
)
|
|
123
|
+
locals.push(new_pin)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# @param facts_by_pin [Hash{Pin::LocalVariable => Array<Hash{Symbol => String}>}]
|
|
127
|
+
# @param presences [Array<Range>]
|
|
128
|
+
# @return [void]
|
|
129
|
+
def process_facts(facts_by_pin, presences)
|
|
130
|
+
#
|
|
131
|
+
# Add specialized locals for the rest of the block
|
|
132
|
+
#
|
|
133
|
+
facts_by_pin.each_pair do |pin, facts|
|
|
134
|
+
facts.each do |fact|
|
|
135
|
+
downcast_type_name = fact.fetch(:type)
|
|
136
|
+
presences.each do |presence|
|
|
137
|
+
add_downcast_local(pin, downcast_type_name, presence)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# @param conditional_node [Parser::AST::Node]
|
|
144
|
+
def process_conditional(conditional_node, true_ranges)
|
|
145
|
+
if conditional_node.type == :send
|
|
146
|
+
process_isa(conditional_node, true_ranges)
|
|
147
|
+
elsif conditional_node.type == :and
|
|
148
|
+
process_and(conditional_node, true_ranges)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# @param isa_node [Parser::AST::Node]
|
|
153
|
+
# @return [Array(String, String)]
|
|
154
|
+
def parse_isa(isa_node)
|
|
155
|
+
return unless isa_node.type == :send && isa_node.children[1] == :is_a?
|
|
156
|
+
# Check if conditional node follows this pattern:
|
|
157
|
+
# s(:send,
|
|
158
|
+
# s(:send, nil, :foo), :is_a?,
|
|
159
|
+
# s(:const, nil, :Baz)),
|
|
160
|
+
isa_receiver = isa_node.children[0]
|
|
161
|
+
isa_type_name = type_name(isa_node.children[2])
|
|
162
|
+
return unless isa_type_name
|
|
163
|
+
|
|
164
|
+
# check if isa_receiver looks like this:
|
|
165
|
+
# s(:send, nil, :foo)
|
|
166
|
+
# and set variable_name to :foo
|
|
167
|
+
if isa_receiver.type == :send && isa_receiver.children[0].nil? && isa_receiver.children[1].is_a?(Symbol)
|
|
168
|
+
variable_name = isa_receiver.children[1].to_s
|
|
169
|
+
end
|
|
170
|
+
# or like this:
|
|
171
|
+
# (lvar :repr)
|
|
172
|
+
variable_name = isa_receiver.children[0].to_s if isa_receiver.type == :lvar
|
|
173
|
+
return unless variable_name
|
|
174
|
+
|
|
175
|
+
[isa_type_name, variable_name]
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def find_local(variable_name, position)
|
|
179
|
+
pins = locals.select { |pin| pin.name == variable_name && pin.presence.include?(position) }
|
|
180
|
+
return unless pins.length == 1
|
|
181
|
+
pins.first
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def process_isa(isa_node, true_presences)
|
|
185
|
+
isa_type_name, variable_name = parse_isa(isa_node)
|
|
186
|
+
return if variable_name.nil? || variable_name.empty?
|
|
187
|
+
isa_position = Range.from_node(isa_node).start
|
|
188
|
+
|
|
189
|
+
pin = find_local(variable_name, isa_position)
|
|
190
|
+
return unless pin
|
|
191
|
+
|
|
192
|
+
if_true = {}
|
|
193
|
+
if_true[pin] ||= []
|
|
194
|
+
if_true[pin] << { type: isa_type_name }
|
|
195
|
+
process_facts(if_true, true_presences)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# @param node [Parser::AST::Node]
|
|
199
|
+
def type_name(node)
|
|
200
|
+
# e.g.,
|
|
201
|
+
# s(:const, nil, :Baz)
|
|
202
|
+
return unless node.type == :const
|
|
203
|
+
module_node = node.children[0]
|
|
204
|
+
class_node = node.children[1]
|
|
205
|
+
|
|
206
|
+
return class_node.to_s if module_node.nil?
|
|
207
|
+
|
|
208
|
+
module_type_name = type_name(module_node)
|
|
209
|
+
return unless module_type_name
|
|
210
|
+
|
|
211
|
+
"#{module_type_name}::#{class_node}"
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# @todo "return type could not be inferred" should not trigger here
|
|
215
|
+
# @sg-ignore
|
|
216
|
+
# @param clause_node [Parser::AST::Node]
|
|
217
|
+
def always_breaks?(clause_node)
|
|
218
|
+
clause_node&.type == :break
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
attr_reader :locals
|
|
222
|
+
|
|
223
|
+
attr_reader :enclosing_breakable_pin
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
@@ -74,10 +74,24 @@ module Solargraph
|
|
|
74
74
|
|
|
75
75
|
# @abstract
|
|
76
76
|
# @param node [Parser::AST::Node]
|
|
77
|
-
# @return [Hash{Parser::AST::Node => Chain}]
|
|
77
|
+
# @return [Hash{Parser::AST::Node => Source::Chain}]
|
|
78
78
|
def convert_hash node
|
|
79
79
|
raise NotImplementedError
|
|
80
80
|
end
|
|
81
|
+
|
|
82
|
+
# @abstract
|
|
83
|
+
# @param node [Parser::AST::Node]
|
|
84
|
+
# @return [Position]
|
|
85
|
+
def get_node_start_position(node)
|
|
86
|
+
raise NotImplementedError
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @abstract
|
|
90
|
+
# @param node [Parser::AST::Node]
|
|
91
|
+
# @return [Position]
|
|
92
|
+
def get_node_end_position(node)
|
|
93
|
+
raise NotImplementedError
|
|
94
|
+
end
|
|
81
95
|
end
|
|
82
96
|
end
|
|
83
97
|
end
|
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
require 'parser/current'
|
|
4
4
|
require 'parser/source/buffer'
|
|
5
5
|
|
|
6
|
+
# Awaiting ability to use a version containing https://github.com/whitequark/parser/pull/1076
|
|
7
|
+
#
|
|
8
|
+
# @!parse
|
|
9
|
+
# class ::Parser::Base < ::Parser::Builder
|
|
10
|
+
# # @return [Integer]
|
|
11
|
+
# def version; end
|
|
12
|
+
# end
|
|
13
|
+
# class ::Parser::CurrentRuby < ::Parser::Base; end
|
|
14
|
+
|
|
6
15
|
module Solargraph
|
|
7
16
|
module Parser
|
|
8
17
|
module ParserGem
|
|
@@ -11,13 +20,9 @@ module Solargraph
|
|
|
11
20
|
# @param filename [String, nil]
|
|
12
21
|
# @return [Array(Parser::AST::Node, Hash{Integer => String})]
|
|
13
22
|
def parse_with_comments code, filename = nil
|
|
14
|
-
|
|
15
|
-
buffer.source = code
|
|
16
|
-
node = parser.parse(buffer)
|
|
23
|
+
node = parse(code, filename)
|
|
17
24
|
comments = CommentRipper.new(code, filename, 0).parse
|
|
18
25
|
[node, comments]
|
|
19
|
-
rescue ::Parser::SyntaxError => e
|
|
20
|
-
raise Parser::SyntaxError, e.message
|
|
21
26
|
end
|
|
22
27
|
|
|
23
28
|
# @param code [String]
|
|
@@ -28,7 +33,7 @@ module Solargraph
|
|
|
28
33
|
buffer = ::Parser::Source::Buffer.new(filename, line)
|
|
29
34
|
buffer.source = code
|
|
30
35
|
parser.parse(buffer)
|
|
31
|
-
rescue ::Parser::SyntaxError => e
|
|
36
|
+
rescue ::Parser::SyntaxError, ::Parser::UnknownEncodingInMagicComment => e
|
|
32
37
|
raise Parser::SyntaxError, e.message
|
|
33
38
|
end
|
|
34
39
|
|
|
@@ -57,22 +57,22 @@ module Solargraph
|
|
|
57
57
|
elsif n.type == :send
|
|
58
58
|
if n.children[0].is_a?(::Parser::AST::Node)
|
|
59
59
|
result.concat generate_links(n.children[0])
|
|
60
|
-
result.push Chain::Call.new(n.children[1].to_s, node_args(n), passed_block(n))
|
|
60
|
+
result.push Chain::Call.new(n.children[1].to_s, Location.from_node(n), node_args(n), passed_block(n))
|
|
61
61
|
elsif n.children[0].nil?
|
|
62
62
|
args = []
|
|
63
63
|
n.children[2..-1].each do |c|
|
|
64
64
|
args.push NodeChainer.chain(c, @filename, n)
|
|
65
65
|
end
|
|
66
|
-
result.push Chain::Call.new(n.children[1].to_s, node_args(n), passed_block(n))
|
|
66
|
+
result.push Chain::Call.new(n.children[1].to_s, Location.from_node(n), node_args(n), passed_block(n))
|
|
67
67
|
else
|
|
68
68
|
raise "No idea what to do with #{n}"
|
|
69
69
|
end
|
|
70
70
|
elsif n.type == :csend
|
|
71
71
|
if n.children[0].is_a?(::Parser::AST::Node)
|
|
72
72
|
result.concat generate_links(n.children[0])
|
|
73
|
-
result.push Chain::QCall.new(n.children[1].to_s, node_args(n))
|
|
73
|
+
result.push Chain::QCall.new(n.children[1].to_s, Location.from_node(n), node_args(n))
|
|
74
74
|
elsif n.children[0].nil?
|
|
75
|
-
result.push Chain::QCall.new(n.children[1].to_s, node_args(n))
|
|
75
|
+
result.push Chain::QCall.new(n.children[1].to_s, Location.from_node(n), node_args(n))
|
|
76
76
|
else
|
|
77
77
|
raise "No idea what to do with #{n}"
|
|
78
78
|
end
|
|
@@ -82,15 +82,15 @@ module Solargraph
|
|
|
82
82
|
result.push Chain::ZSuper.new('super')
|
|
83
83
|
elsif n.type == :super
|
|
84
84
|
args = n.children.map { |c| NodeChainer.chain(c, @filename, n) }
|
|
85
|
-
result.push Chain::Call.new('super', args)
|
|
85
|
+
result.push Chain::Call.new('super', Location.from_node(n), args)
|
|
86
86
|
elsif n.type == :yield
|
|
87
87
|
args = n.children.map { |c| NodeChainer.chain(c, @filename, n) }
|
|
88
|
-
result.push Chain::Call.new('yield', args)
|
|
88
|
+
result.push Chain::Call.new('yield', Location.from_node(n), args)
|
|
89
89
|
elsif n.type == :const
|
|
90
90
|
const = unpack_name(n)
|
|
91
91
|
result.push Chain::Constant.new(const)
|
|
92
92
|
elsif [:lvar, :lvasgn].include?(n.type)
|
|
93
|
-
result.push Chain::Call.new(n.children[0].to_s)
|
|
93
|
+
result.push Chain::Call.new(n.children[0].to_s, Location.from_node(n))
|
|
94
94
|
elsif [:ivar, :ivasgn].include?(n.type)
|
|
95
95
|
result.push Chain::InstanceVariable.new(n.children[0].to_s)
|
|
96
96
|
elsif [:cvar, :cvasgn].include?(n.type)
|
|
@@ -124,13 +124,13 @@ module Solargraph
|
|
|
124
124
|
end
|
|
125
125
|
end
|
|
126
126
|
elsif n.type == :hash
|
|
127
|
-
result.push Chain::Hash.new('::Hash', hash_is_splatted?(n))
|
|
127
|
+
result.push Chain::Hash.new('::Hash', n, hash_is_splatted?(n))
|
|
128
128
|
elsif n.type == :array
|
|
129
129
|
chained_children = n.children.map { |c| NodeChainer.chain(c) }
|
|
130
|
-
result.push Source::Chain::Array.new(chained_children)
|
|
130
|
+
result.push Source::Chain::Array.new(chained_children, n)
|
|
131
131
|
else
|
|
132
132
|
lit = infer_literal_node_type(n)
|
|
133
|
-
result.push (lit ? Chain::Literal.new(lit) : Chain::Link.new)
|
|
133
|
+
result.push (lit ? Chain::Literal.new(lit, n) : Chain::Link.new)
|
|
134
134
|
end
|
|
135
135
|
result
|
|
136
136
|
end
|
|
@@ -12,7 +12,7 @@ require 'ast'
|
|
|
12
12
|
# class Node
|
|
13
13
|
# # New children
|
|
14
14
|
#
|
|
15
|
-
# # @return [Array<
|
|
15
|
+
# # @return [Array<self>]
|
|
16
16
|
# attr_reader :children
|
|
17
17
|
# end
|
|
18
18
|
# end
|
|
@@ -40,7 +40,7 @@ module Solargraph
|
|
|
40
40
|
if n.is_a?(AST::Node)
|
|
41
41
|
if n.type == :cbase
|
|
42
42
|
parts = [''] + pack_name(n)
|
|
43
|
-
|
|
43
|
+
elsif n.type == :const
|
|
44
44
|
parts += pack_name(n)
|
|
45
45
|
end
|
|
46
46
|
else
|
|
@@ -59,6 +59,8 @@ module Solargraph
|
|
|
59
59
|
return '::String'
|
|
60
60
|
elsif node.type == :array
|
|
61
61
|
return '::Array'
|
|
62
|
+
elsif node.type == :nil
|
|
63
|
+
return '::NilClass'
|
|
62
64
|
elsif node.type == :hash
|
|
63
65
|
return '::Hash'
|
|
64
66
|
elsif node.type == :int
|
|
@@ -311,7 +313,7 @@ module Solargraph
|
|
|
311
313
|
# statements in value positions.
|
|
312
314
|
module DeepInference
|
|
313
315
|
class << self
|
|
314
|
-
CONDITIONAL_ALL_BUT_FIRST = [:if, :unless
|
|
316
|
+
CONDITIONAL_ALL_BUT_FIRST = [:if, :unless]
|
|
315
317
|
CONDITIONAL_ALL = [:or]
|
|
316
318
|
ONLY_ONE_CHILD = [:return]
|
|
317
319
|
FIRST_TWO_CHILDREN = [:rescue]
|
|
@@ -462,7 +464,7 @@ module Solargraph
|
|
|
462
464
|
result
|
|
463
465
|
end
|
|
464
466
|
|
|
465
|
-
# @param nodes [Enumerable<Parser::AST::Node,
|
|
467
|
+
# @param nodes [Enumerable<Parser::AST::Node, BasicObject>]
|
|
466
468
|
# @return [Array<Parser::AST::Node, nil>]
|
|
467
469
|
def reduce_to_value_nodes nodes
|
|
468
470
|
result = []
|