solargraph 0.54.4 → 0.56.2
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/.github/workflows/plugins.yml +2 -0
- data/.github/workflows/typecheck.yml +3 -1
- data/.gitignore +2 -0
- data/CHANGELOG.md +62 -0
- data/README.md +13 -3
- data/lib/solargraph/api_map/index.rb +24 -16
- data/lib/solargraph/api_map/store.rb +48 -23
- data/lib/solargraph/api_map.rb +175 -77
- data/lib/solargraph/bench.rb +17 -1
- data/lib/solargraph/complex_type/type_methods.rb +6 -1
- data/lib/solargraph/complex_type/unique_type.rb +98 -9
- data/lib/solargraph/complex_type.rb +35 -6
- data/lib/solargraph/convention/base.rb +3 -3
- data/lib/solargraph/convention/data_definition/data_assignment_node.rb +60 -0
- data/lib/solargraph/convention/data_definition/data_definition_node.rb +89 -0
- data/lib/solargraph/convention/data_definition.rb +104 -0
- data/lib/solargraph/convention/gemspec.rb +2 -1
- data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +60 -0
- data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +100 -0
- data/lib/solargraph/convention/struct_definition.rb +141 -0
- data/lib/solargraph/convention.rb +5 -3
- data/lib/solargraph/doc_map.rb +277 -57
- data/lib/solargraph/gem_pins.rb +53 -37
- data/lib/solargraph/language_server/host/message_worker.rb +10 -7
- data/lib/solargraph/language_server/host.rb +12 -2
- data/lib/solargraph/language_server/message/extended/check_gem_version.rb +2 -0
- data/lib/solargraph/language_server/message/extended/document.rb +5 -2
- data/lib/solargraph/language_server/message/extended/document_gems.rb +3 -3
- data/lib/solargraph/library.rb +45 -17
- data/lib/solargraph/location.rb +21 -0
- data/lib/solargraph/logging.rb +1 -0
- data/lib/solargraph/parser/comment_ripper.rb +12 -6
- data/lib/solargraph/parser/flow_sensitive_typing.rb +227 -0
- data/lib/solargraph/parser/node_methods.rb +14 -0
- data/lib/solargraph/parser/node_processor/base.rb +9 -4
- data/lib/solargraph/parser/node_processor.rb +21 -8
- data/lib/solargraph/parser/parser_gem/class_methods.rb +16 -14
- data/lib/solargraph/parser/parser_gem/node_chainer.rb +10 -10
- data/lib/solargraph/parser/parser_gem/node_methods.rb +4 -2
- data/lib/solargraph/parser/parser_gem/node_processors/alias_node.rb +2 -1
- data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +21 -0
- data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +4 -2
- data/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +4 -2
- data/lib/solargraph/parser/parser_gem/node_processors/casgn_node.rb +2 -1
- data/lib/solargraph/parser/parser_gem/node_processors/cvasgn_node.rb +2 -1
- data/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +6 -3
- data/lib/solargraph/parser/parser_gem/node_processors/defs_node.rb +2 -1
- data/lib/solargraph/parser/parser_gem/node_processors/gvasgn_node.rb +2 -1
- data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +21 -0
- data/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +4 -2
- data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +2 -1
- data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +4 -1
- data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +8 -7
- data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +42 -0
- data/lib/solargraph/parser/parser_gem/node_processors/orasgn_node.rb +1 -0
- data/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb +3 -1
- data/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +4 -3
- data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +28 -16
- data/lib/solargraph/parser/parser_gem/node_processors/sym_node.rb +3 -1
- data/lib/solargraph/parser/parser_gem/node_processors/until_node.rb +29 -0
- data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +29 -0
- data/lib/solargraph/parser/parser_gem/node_processors.rb +14 -0
- data/lib/solargraph/parser/region.rb +1 -1
- data/lib/solargraph/parser.rb +1 -0
- data/lib/solargraph/pin/base.rb +316 -28
- data/lib/solargraph/pin/base_variable.rb +16 -9
- data/lib/solargraph/pin/block.rb +2 -0
- data/lib/solargraph/pin/breakable.rb +9 -0
- data/lib/solargraph/pin/callable.rb +74 -3
- data/lib/solargraph/pin/closure.rb +18 -1
- data/lib/solargraph/pin/common.rb +5 -0
- data/lib/solargraph/pin/delegated_method.rb +20 -1
- data/lib/solargraph/pin/documenting.rb +16 -0
- data/lib/solargraph/pin/keyword.rb +7 -2
- data/lib/solargraph/pin/local_variable.rb +15 -6
- data/lib/solargraph/pin/method.rb +169 -43
- data/lib/solargraph/pin/namespace.rb +17 -9
- data/lib/solargraph/pin/parameter.rb +60 -11
- data/lib/solargraph/pin/proxy_type.rb +12 -6
- data/lib/solargraph/pin/reference/override.rb +10 -6
- data/lib/solargraph/pin/reference/require.rb +2 -2
- data/lib/solargraph/pin/signature.rb +42 -0
- data/lib/solargraph/pin/singleton.rb +1 -1
- data/lib/solargraph/pin/symbol.rb +3 -2
- data/lib/solargraph/pin/until.rb +18 -0
- data/lib/solargraph/pin/while.rb +18 -0
- data/lib/solargraph/pin.rb +4 -1
- data/lib/solargraph/pin_cache.rb +185 -0
- data/lib/solargraph/position.rb +9 -0
- data/lib/solargraph/range.rb +9 -0
- data/lib/solargraph/rbs_map/conversions.rb +221 -67
- data/lib/solargraph/rbs_map/core_fills.rb +32 -16
- data/lib/solargraph/rbs_map/core_map.rb +34 -11
- data/lib/solargraph/rbs_map/stdlib_map.rb +15 -5
- data/lib/solargraph/rbs_map.rb +74 -17
- data/lib/solargraph/shell.rb +17 -18
- data/lib/solargraph/source/chain/array.rb +11 -7
- 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 +53 -23
- data/lib/solargraph/source/chain/constant.rb +1 -1
- data/lib/solargraph/source/chain/hash.rb +4 -3
- data/lib/solargraph/source/chain/head.rb +1 -1
- data/lib/solargraph/source/chain/if.rb +1 -1
- data/lib/solargraph/source/chain/link.rb +2 -0
- data/lib/solargraph/source/chain/literal.rb +22 -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 +78 -48
- data/lib/solargraph/source/source_chainer.rb +2 -2
- data/lib/solargraph/source_map/clip.rb +3 -1
- data/lib/solargraph/source_map/mapper.rb +9 -5
- data/lib/solargraph/source_map.rb +0 -17
- data/lib/solargraph/type_checker/checks.rb +4 -0
- data/lib/solargraph/type_checker.rb +35 -8
- data/lib/solargraph/version.rb +1 -1
- data/lib/solargraph/views/_method.erb +10 -10
- data/lib/solargraph/views/_namespace.erb +3 -3
- data/lib/solargraph/views/document.erb +10 -10
- data/lib/solargraph/workspace/config.rb +1 -1
- data/lib/solargraph/workspace.rb +23 -5
- data/lib/solargraph/yard_map/helpers.rb +29 -1
- data/lib/solargraph/yard_map/mapper/to_constant.rb +7 -5
- data/lib/solargraph/yard_map/mapper/to_method.rb +53 -18
- data/lib/solargraph/yard_map/mapper/to_namespace.rb +9 -7
- data/lib/solargraph/yard_map/mapper.rb +4 -3
- data/lib/solargraph/yard_map/to_method.rb +4 -2
- data/lib/solargraph/yardoc.rb +7 -8
- data/lib/solargraph.rb +32 -1
- data/rbs/fills/tuple.rbs +150 -0
- data/rbs_collection.yaml +19 -0
- data/solargraph.gemspec +2 -1
- metadata +37 -9
- data/lib/solargraph/cache.rb +0 -77
data/lib/solargraph/library.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'pathname'
|
4
4
|
require 'observer'
|
5
|
+
require 'open3'
|
5
6
|
|
6
7
|
module Solargraph
|
7
8
|
# A Library handles coordination between a Workspace and an ApiMap.
|
@@ -327,9 +328,10 @@ module Solargraph
|
|
327
328
|
|
328
329
|
# @param query [String]
|
329
330
|
# @return [Enumerable<YARD::CodeObjects::Base>]
|
331
|
+
# @return [Array(ApiMap, Enumerable<Pin::Base>)]
|
330
332
|
def document query
|
331
333
|
sync_catalog
|
332
|
-
mutex.synchronize { api_map.
|
334
|
+
mutex.synchronize { [api_map, api_map.get_path_pins(query)] }
|
333
335
|
end
|
334
336
|
|
335
337
|
# @param query [String]
|
@@ -430,7 +432,8 @@ module Solargraph
|
|
430
432
|
Bench.new(
|
431
433
|
source_maps: source_map_hash.values,
|
432
434
|
workspace: workspace,
|
433
|
-
external_requires: external_requires
|
435
|
+
external_requires: external_requires,
|
436
|
+
live_map: @current ? source_map_hash[@current.filename] : nil
|
434
437
|
)
|
435
438
|
end
|
436
439
|
|
@@ -586,28 +589,52 @@ module Solargraph
|
|
586
589
|
# @return [void]
|
587
590
|
def cache_next_gemspec
|
588
591
|
return if @cache_progress
|
589
|
-
|
592
|
+
|
593
|
+
spec = cacheable_specs.first
|
590
594
|
return end_cache_progress unless spec
|
591
595
|
|
592
596
|
pending = api_map.uncached_gemspecs.length - cache_errors.length - 1
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
rescue Errno::EINVAL => _e
|
600
|
-
logger.info "Cached #{spec.name} #{spec.version} with EINVAL"
|
601
|
-
rescue StandardError => e
|
602
|
-
cache_errors.add spec
|
603
|
-
Solargraph.logger.warn "Error caching gemspec #{spec.name} #{spec.version}: [#{e.class}] #{e.message}"
|
604
|
-
ensure
|
605
|
-
end_cache_progress
|
597
|
+
|
598
|
+
if Yardoc.processing?(spec)
|
599
|
+
logger.info "Enqueuing cache of #{spec.name} #{spec.version} (already being processed)"
|
600
|
+
queued_gemspec_cache.push(spec)
|
601
|
+
return if pending - queued_gemspec_cache.length < 1
|
602
|
+
|
606
603
|
catalog
|
607
604
|
sync_catalog
|
605
|
+
else
|
606
|
+
logger.info "Caching #{spec.name} #{spec.version}"
|
607
|
+
Thread.new do
|
608
|
+
report_cache_progress spec.name, pending
|
609
|
+
_o, e, s = Open3.capture3(workspace.command_path, 'cache', spec.name, spec.version.to_s)
|
610
|
+
if s.success?
|
611
|
+
logger.info "Cached #{spec.name} #{spec.version}"
|
612
|
+
else
|
613
|
+
cache_errors.add spec
|
614
|
+
logger.warn "Error caching gemspec #{spec.name} #{spec.version}"
|
615
|
+
logger.warn e
|
616
|
+
end
|
617
|
+
end_cache_progress
|
618
|
+
catalog
|
619
|
+
sync_catalog
|
620
|
+
end
|
608
621
|
end
|
609
622
|
end
|
610
623
|
|
624
|
+
def cacheable_specs
|
625
|
+
cacheable = api_map.uncached_yard_gemspecs +
|
626
|
+
api_map.uncached_rbs_collection_gemspecs -
|
627
|
+
queued_gemspec_cache -
|
628
|
+
cache_errors.to_a
|
629
|
+
return cacheable unless cacheable.empty?
|
630
|
+
|
631
|
+
queued_gemspec_cache
|
632
|
+
end
|
633
|
+
|
634
|
+
def queued_gemspec_cache
|
635
|
+
@queued_gemspec_cache ||= []
|
636
|
+
end
|
637
|
+
|
611
638
|
# @param gem_name [String]
|
612
639
|
# @param pending [Integer]
|
613
640
|
# @return [void]
|
@@ -653,7 +680,8 @@ module Solargraph
|
|
653
680
|
api_map.catalog bench
|
654
681
|
source_map_hash.values.each { |map| find_external_requires(map) }
|
655
682
|
logger.info "Catalog complete (#{api_map.source_maps.length} files, #{api_map.pins.length} pins)"
|
656
|
-
logger.info "#{api_map.
|
683
|
+
logger.info "#{api_map.uncached_yard_gemspecs.length} uncached YARD gemspecs"
|
684
|
+
logger.info "#{api_map.uncached_rbs_collection_gemspecs.length} uncached RBS collection gemspecs"
|
657
685
|
cache_next_gemspec
|
658
686
|
@sync_count = 0
|
659
687
|
end
|
data/lib/solargraph/location.rb
CHANGED
@@ -25,11 +25,32 @@ module Solargraph
|
|
25
25
|
[filename, range]
|
26
26
|
end
|
27
27
|
|
28
|
+
def <=>(other)
|
29
|
+
return nil unless other.is_a?(Location)
|
30
|
+
if filename == other.filename
|
31
|
+
range <=> other.range
|
32
|
+
else
|
33
|
+
filename <=> other.filename
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def rbs?
|
38
|
+
filename.end_with?('.rbs')
|
39
|
+
end
|
40
|
+
|
28
41
|
# @param location [self]
|
29
42
|
def contain? location
|
30
43
|
range.contain?(location.range.start) && range.contain?(location.range.ending) && filename == location.filename
|
31
44
|
end
|
32
45
|
|
46
|
+
def inspect
|
47
|
+
"<#{self.class.name}: filename=#{filename}, range=#{range.inspect}>"
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_s
|
51
|
+
inspect
|
52
|
+
end
|
53
|
+
|
33
54
|
# @return [Hash]
|
34
55
|
def to_hash
|
35
56
|
{
|
data/lib/solargraph/logging.rb
CHANGED
@@ -13,6 +13,8 @@ module Solargraph
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def on_comment *args
|
16
|
+
# @sg-ignore
|
17
|
+
# @type [Array(Symbol, String, Array([Integer, nil], [Integer, nil]))]
|
16
18
|
result = super
|
17
19
|
if @buffer_lines[result[2][0]][0..result[2][1]].strip =~ /^#/
|
18
20
|
chomped = result[1].chomp
|
@@ -24,24 +26,28 @@ module Solargraph
|
|
24
26
|
result
|
25
27
|
end
|
26
28
|
|
29
|
+
# @param result [Array(Symbol, String, Array([Integer, nil], [Integer, nil]))]
|
30
|
+
# @return [void]
|
31
|
+
def create_snippet(result)
|
32
|
+
chomped = result[1].chomp
|
33
|
+
@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)
|
34
|
+
end
|
35
|
+
|
27
36
|
def on_embdoc_beg *args
|
28
37
|
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)
|
38
|
+
create_snippet(result)
|
31
39
|
result
|
32
40
|
end
|
33
41
|
|
34
42
|
def on_embdoc *args
|
35
43
|
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)
|
44
|
+
create_snippet(result)
|
38
45
|
result
|
39
46
|
end
|
40
47
|
|
41
48
|
def on_embdoc_end *args
|
42
49
|
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)
|
50
|
+
create_snippet(result)
|
45
51
|
result
|
46
52
|
end
|
47
53
|
|
@@ -0,0 +1,227 @@
|
|
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
|
+
source: :flow_sensitive_typing
|
123
|
+
)
|
124
|
+
locals.push(new_pin)
|
125
|
+
end
|
126
|
+
|
127
|
+
# @param facts_by_pin [Hash{Pin::LocalVariable => Array<Hash{Symbol => String}>}]
|
128
|
+
# @param presences [Array<Range>]
|
129
|
+
# @return [void]
|
130
|
+
def process_facts(facts_by_pin, presences)
|
131
|
+
#
|
132
|
+
# Add specialized locals for the rest of the block
|
133
|
+
#
|
134
|
+
facts_by_pin.each_pair do |pin, facts|
|
135
|
+
facts.each do |fact|
|
136
|
+
downcast_type_name = fact.fetch(:type)
|
137
|
+
presences.each do |presence|
|
138
|
+
add_downcast_local(pin, downcast_type_name, presence)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# @param conditional_node [Parser::AST::Node]
|
145
|
+
def process_conditional(conditional_node, true_ranges)
|
146
|
+
if conditional_node.type == :send
|
147
|
+
process_isa(conditional_node, true_ranges)
|
148
|
+
elsif conditional_node.type == :and
|
149
|
+
process_and(conditional_node, true_ranges)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# @param isa_node [Parser::AST::Node]
|
154
|
+
# @return [Array(String, String)]
|
155
|
+
def parse_isa(isa_node)
|
156
|
+
return unless isa_node&.type == :send && isa_node.children[1] == :is_a?
|
157
|
+
# Check if conditional node follows this pattern:
|
158
|
+
# s(:send,
|
159
|
+
# s(:send, nil, :foo), :is_a?,
|
160
|
+
# s(:const, nil, :Baz)),
|
161
|
+
isa_receiver = isa_node.children[0]
|
162
|
+
isa_type_name = type_name(isa_node.children[2])
|
163
|
+
return unless isa_type_name
|
164
|
+
|
165
|
+
# check if isa_receiver looks like this:
|
166
|
+
# s(:send, nil, :foo)
|
167
|
+
# and set variable_name to :foo
|
168
|
+
if isa_receiver&.type == :send && isa_receiver.children[0].nil? && isa_receiver.children[1].is_a?(Symbol)
|
169
|
+
variable_name = isa_receiver.children[1].to_s
|
170
|
+
end
|
171
|
+
# or like this:
|
172
|
+
# (lvar :repr)
|
173
|
+
variable_name = isa_receiver.children[0].to_s if isa_receiver&.type == :lvar
|
174
|
+
return unless variable_name
|
175
|
+
|
176
|
+
[isa_type_name, variable_name]
|
177
|
+
end
|
178
|
+
|
179
|
+
def find_local(variable_name, position)
|
180
|
+
pins = locals.select { |pin| pin.name == variable_name && pin.presence.include?(position) }
|
181
|
+
return unless pins.length == 1
|
182
|
+
pins.first
|
183
|
+
end
|
184
|
+
|
185
|
+
def process_isa(isa_node, true_presences)
|
186
|
+
isa_type_name, variable_name = parse_isa(isa_node)
|
187
|
+
return if variable_name.nil? || variable_name.empty?
|
188
|
+
isa_position = Range.from_node(isa_node).start
|
189
|
+
|
190
|
+
pin = find_local(variable_name, isa_position)
|
191
|
+
return unless pin
|
192
|
+
|
193
|
+
if_true = {}
|
194
|
+
if_true[pin] ||= []
|
195
|
+
if_true[pin] << { type: isa_type_name }
|
196
|
+
process_facts(if_true, true_presences)
|
197
|
+
end
|
198
|
+
|
199
|
+
# @param node [Parser::AST::Node]
|
200
|
+
def type_name(node)
|
201
|
+
# e.g.,
|
202
|
+
# s(:const, nil, :Baz)
|
203
|
+
return unless node.type == :const
|
204
|
+
module_node = node.children[0]
|
205
|
+
class_node = node.children[1]
|
206
|
+
|
207
|
+
return class_node.to_s if module_node.nil?
|
208
|
+
|
209
|
+
module_type_name = type_name(module_node)
|
210
|
+
return unless module_type_name
|
211
|
+
|
212
|
+
"#{module_type_name}::#{class_node}"
|
213
|
+
end
|
214
|
+
|
215
|
+
# @todo "return type could not be inferred" should not trigger here
|
216
|
+
# @sg-ignore
|
217
|
+
# @param clause_node [Parser::AST::Node]
|
218
|
+
def always_breaks?(clause_node)
|
219
|
+
clause_node&.type == :break
|
220
|
+
end
|
221
|
+
|
222
|
+
attr_reader :locals
|
223
|
+
|
224
|
+
attr_reader :enclosing_breakable_pin
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
@@ -78,6 +78,20 @@ module Solargraph
|
|
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
|
@@ -30,9 +30,12 @@ module Solargraph
|
|
30
30
|
|
31
31
|
# Subclasses should override this method to generate new pins.
|
32
32
|
#
|
33
|
-
# @return [
|
33
|
+
# @return [Boolean] continue processing the next processor of the same node type.
|
34
|
+
# @return [void] In case there is only one processor registered for the node type, it can be void.
|
34
35
|
def process
|
35
36
|
process_children
|
37
|
+
|
38
|
+
true
|
36
39
|
end
|
37
40
|
|
38
41
|
private
|
@@ -64,7 +67,9 @@ module Solargraph
|
|
64
67
|
# @param position [Solargraph::Position]
|
65
68
|
# @return [Pin::Closure, nil]
|
66
69
|
def named_path_pin position
|
67
|
-
pins.select
|
70
|
+
pins.select do |pin|
|
71
|
+
pin.is_a?(Pin::Closure) && pin.path && !pin.path.empty? && pin.location.range.contain?(position)
|
72
|
+
end.last
|
68
73
|
end
|
69
74
|
|
70
75
|
# @todo Candidate for deprecation
|
@@ -72,14 +77,14 @@ module Solargraph
|
|
72
77
|
# @return [Pin::Closure, nil]
|
73
78
|
def block_pin position
|
74
79
|
# @todo determine if this can return a Pin::Block
|
75
|
-
pins.select{|pin| pin.is_a?(Pin::Closure) && pin.location.range.contain?(position)}.last
|
80
|
+
pins.select { |pin| pin.is_a?(Pin::Closure) && pin.location.range.contain?(position) }.last
|
76
81
|
end
|
77
82
|
|
78
83
|
# @todo Candidate for deprecation
|
79
84
|
# @param position [Solargraph::Position]
|
80
85
|
# @return [Pin::Closure, nil]
|
81
86
|
def closure_pin position
|
82
|
-
pins.select{|pin| pin.is_a?(Pin::Closure) && pin.location.range.contain?(position)}.last
|
87
|
+
pins.select { |pin| pin.is_a?(Pin::Closure) && pin.location.range.contain?(position) }.last
|
83
88
|
end
|
84
89
|
end
|
85
90
|
end
|
@@ -9,16 +9,22 @@ module Solargraph
|
|
9
9
|
autoload :Base, 'solargraph/parser/node_processor/base'
|
10
10
|
|
11
11
|
class << self
|
12
|
-
# @type [Hash<Symbol, Class<NodeProcessor::Base
|
12
|
+
# @type [Hash<Symbol, Array<Class<NodeProcessor::Base>>>]
|
13
13
|
@@processors ||= {}
|
14
14
|
|
15
|
-
# Register a processor for a node type.
|
15
|
+
# Register a processor for a node type. You can register multiple processors for the same type.
|
16
|
+
# If a node processor returns true, it will skip the next processor of the same node type.
|
16
17
|
#
|
17
18
|
# @param type [Symbol]
|
18
19
|
# @param cls [Class<NodeProcessor::Base>]
|
19
20
|
# @return [Class<NodeProcessor::Base>]
|
20
21
|
def register type, cls
|
21
|
-
@@processors[type]
|
22
|
+
@@processors[type] ||= []
|
23
|
+
@@processors[type] << cls
|
24
|
+
end
|
25
|
+
|
26
|
+
def deregister type, cls
|
27
|
+
@@processors[type].delete(cls)
|
22
28
|
end
|
23
29
|
end
|
24
30
|
|
@@ -31,14 +37,21 @@ module Solargraph
|
|
31
37
|
if pins.empty?
|
32
38
|
pins.push Pin::Namespace.new(
|
33
39
|
location: region.source.location,
|
34
|
-
name: ''
|
40
|
+
name: '',
|
41
|
+
source: :parser,
|
35
42
|
)
|
36
43
|
end
|
37
44
|
return [pins, locals] unless Parser.is_ast_node?(node)
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
45
|
+
node_processor_classes = @@processors[node.type] || [NodeProcessor::Base]
|
46
|
+
|
47
|
+
node_processor_classes.each do |klass|
|
48
|
+
processor = klass.new(node, region, pins, locals)
|
49
|
+
process_next = processor.process
|
50
|
+
|
51
|
+
break unless process_next
|
52
|
+
end
|
53
|
+
|
54
|
+
[pins, locals]
|
42
55
|
end
|
43
56
|
end
|
44
57
|
end
|
@@ -1,7 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
4
|
-
|
3
|
+
require 'prism'
|
4
|
+
|
5
|
+
# Awaiting ability to use a version containing https://github.com/whitequark/parser/pull/1076
|
6
|
+
#
|
7
|
+
# @!parse
|
8
|
+
# class ::Parser::Base < ::Parser::Builder
|
9
|
+
# # @return [Integer]
|
10
|
+
# def version; end
|
11
|
+
# end
|
12
|
+
# class ::Parser::CurrentRuby < ::Parser::Base; end
|
5
13
|
|
6
14
|
module Solargraph
|
7
15
|
module Parser
|
@@ -11,13 +19,9 @@ module Solargraph
|
|
11
19
|
# @param filename [String, nil]
|
12
20
|
# @return [Array(Parser::AST::Node, Hash{Integer => String})]
|
13
21
|
def parse_with_comments code, filename = nil
|
14
|
-
|
15
|
-
buffer.source = code
|
16
|
-
node = parser.parse(buffer)
|
22
|
+
node = parse(code, filename)
|
17
23
|
comments = CommentRipper.new(code, filename, 0).parse
|
18
24
|
[node, comments]
|
19
|
-
rescue ::Parser::SyntaxError => e
|
20
|
-
raise Parser::SyntaxError, e.message
|
21
25
|
end
|
22
26
|
|
23
27
|
# @param code [String]
|
@@ -28,18 +32,16 @@ module Solargraph
|
|
28
32
|
buffer = ::Parser::Source::Buffer.new(filename, line)
|
29
33
|
buffer.source = code
|
30
34
|
parser.parse(buffer)
|
31
|
-
rescue ::Parser::SyntaxError => e
|
35
|
+
rescue ::Parser::SyntaxError, ::Parser::UnknownEncodingInMagicComment => e
|
32
36
|
raise Parser::SyntaxError, e.message
|
33
37
|
end
|
34
38
|
|
35
39
|
# @return [::Parser::Base]
|
36
40
|
def parser
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
parser.diagnostics.ignore_warnings = true
|
42
|
-
parser
|
41
|
+
@parser ||= Prism::Translation::Parser.new(FlawedBuilder.new).tap do |parser|
|
42
|
+
parser.diagnostics.all_errors_are_fatal = true
|
43
|
+
parser.diagnostics.ignore_warnings = true
|
44
|
+
end
|
43
45
|
end
|
44
46
|
|
45
47
|
# @param source [Source]
|
@@ -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<self>]
|
15
|
+
# # @return [Array<self, Integer, String, Symbol, nil>]
|
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
|