solargraph 0.54.4 → 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 +18 -0
- data/lib/solargraph/api_map/index.rb +1 -1
- data/lib/solargraph/api_map/store.rb +40 -19
- data/lib/solargraph/api_map.rb +24 -19
- data/lib/solargraph/bench.rb +17 -1
- data/lib/solargraph/complex_type/unique_type.rb +88 -7
- data/lib/solargraph/complex_type.rb +35 -6
- 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 +1 -0
- data/lib/solargraph/doc_map.rb +42 -18
- data/lib/solargraph/language_server/host/message_worker.rb +10 -7
- data/lib/solargraph/language_server/host.rb +1 -0
- data/lib/solargraph/library.rb +2 -1
- data/lib/solargraph/location.rb +8 -0
- 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 +14 -0
- data/lib/solargraph/parser/node_processor.rb +0 -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 +3 -1
- data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +21 -0
- 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/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/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 +1 -0
- data/lib/solargraph/pin/base.rb +9 -3
- data/lib/solargraph/pin/base_variable.rb +7 -1
- data/lib/solargraph/pin/block.rb +2 -0
- data/lib/solargraph/pin/breakable.rb +9 -0
- data/lib/solargraph/pin/local_variable.rb +7 -1
- data/lib/solargraph/pin/method.rb +20 -18
- data/lib/solargraph/pin/namespace.rb +10 -7
- data/lib/solargraph/pin/parameter.rb +13 -5
- data/lib/solargraph/pin/proxy_type.rb +12 -6
- data/lib/solargraph/pin/until.rb +18 -0
- data/lib/solargraph/pin/while.rb +18 -0
- data/lib/solargraph/pin.rb +3 -0
- data/lib/solargraph/rbs_map/conversions.rb +8 -8
- data/lib/solargraph/rbs_map/core_fills.rb +10 -3
- data/lib/solargraph/source/chain/array.rb +4 -3
- data/lib/solargraph/source/chain/call.rb +46 -17
- data/lib/solargraph/source/chain/constant.rb +1 -1
- data/lib/solargraph/source/chain/hash.rb +3 -2
- data/lib/solargraph/source/chain/link.rb +2 -0
- data/lib/solargraph/source/chain/literal.rb +22 -2
- data/lib/solargraph/source/chain/z_super.rb +1 -1
- data/lib/solargraph/source/chain.rb +77 -47
- data/lib/solargraph/source/source_chainer.rb +2 -2
- data/lib/solargraph/source_map/clip.rb +3 -1
- 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/yard_map/mapper/to_method.rb +42 -15
- data/lib/solargraph/yardoc.rb +1 -1
- metadata +15 -3
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Solargraph
|
4
|
+
module Convention
|
5
|
+
module StructDefinition
|
6
|
+
autoload :StructDefintionNode, 'solargraph/convention/struct_definition/struct_definition_node'
|
7
|
+
autoload :StructAssignmentNode, 'solargraph/convention/struct_definition/struct_assignment_node'
|
8
|
+
|
9
|
+
module NodeProcessors
|
10
|
+
class StructNode < Parser::NodeProcessor::Base
|
11
|
+
def process
|
12
|
+
return if struct_definition_node.nil?
|
13
|
+
|
14
|
+
loc = get_node_location(node)
|
15
|
+
nspin = Solargraph::Pin::Namespace.new(
|
16
|
+
type: :class,
|
17
|
+
location: loc,
|
18
|
+
closure: region.closure,
|
19
|
+
name: struct_definition_node.class_name,
|
20
|
+
comments: comments_for(node),
|
21
|
+
visibility: :public,
|
22
|
+
gates: region.closure.gates.freeze
|
23
|
+
)
|
24
|
+
pins.push nspin
|
25
|
+
|
26
|
+
# define initialize method
|
27
|
+
initialize_method_pin = Pin::Method.new(
|
28
|
+
name: 'initialize',
|
29
|
+
parameters: [],
|
30
|
+
scope: :instance,
|
31
|
+
location: get_node_location(node),
|
32
|
+
closure: nspin,
|
33
|
+
visibility: :private,
|
34
|
+
comments: comments_for(node)
|
35
|
+
)
|
36
|
+
|
37
|
+
pins.push initialize_method_pin
|
38
|
+
|
39
|
+
struct_definition_node.attributes.map do |attribute_node, attribute_name|
|
40
|
+
initialize_method_pin.parameters.push(
|
41
|
+
Pin::Parameter.new(
|
42
|
+
name: attribute_name,
|
43
|
+
decl: struct_definition_node.keyword_init? ? :kwarg : :arg,
|
44
|
+
location: get_node_location(attribute_node),
|
45
|
+
closure: initialize_method_pin
|
46
|
+
)
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
# define attribute accessors and instance variables
|
51
|
+
struct_definition_node.attributes.each do |attribute_node, attribute_name|
|
52
|
+
[attribute_name, "#{attribute_name}="].each do |name|
|
53
|
+
method_pin = Pin::Method.new(
|
54
|
+
name: name,
|
55
|
+
parameters: [],
|
56
|
+
scope: :instance,
|
57
|
+
location: get_node_location(attribute_node),
|
58
|
+
closure: nspin,
|
59
|
+
comments: attribute_comments(attribute_node, attribute_name),
|
60
|
+
visibility: :public
|
61
|
+
)
|
62
|
+
|
63
|
+
pins.push method_pin
|
64
|
+
|
65
|
+
next unless name.include?('=') # setter
|
66
|
+
pins.push Pin::InstanceVariable.new(name: "@#{attribute_name}",
|
67
|
+
closure: method_pin,
|
68
|
+
location: get_node_location(attribute_node),
|
69
|
+
comments: attribute_comments(attribute_node, attribute_name))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
process_children region.update(closure: nspin, visibility: :public)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# @return [StructDefintionNode, nil]
|
79
|
+
def struct_definition_node
|
80
|
+
@struct_definition_node ||= if StructDefintionNode.valid?(node)
|
81
|
+
StructDefintionNode.new(node)
|
82
|
+
elsif StructAssignmentNode.valid?(node)
|
83
|
+
StructAssignmentNode.new(node)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# @param attribute_node [Parser::AST::Node]
|
88
|
+
# @return [String, nil]
|
89
|
+
def attribute_comments(attribute_node, attribute_name)
|
90
|
+
struct_comments = comments_for(attribute_node)
|
91
|
+
return if struct_comments.nil? || struct_comments.empty?
|
92
|
+
|
93
|
+
struct_comments.split("\n").find do |row|
|
94
|
+
row.include?(attribute_name)
|
95
|
+
end&.gsub('@param', '@return')&.gsub(attribute_name, '')
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -10,6 +10,7 @@ module Solargraph
|
|
10
10
|
autoload :Gemfile, 'solargraph/convention/gemfile'
|
11
11
|
autoload :Gemspec, 'solargraph/convention/gemspec'
|
12
12
|
autoload :Rakefile, 'solargraph/convention/rakefile'
|
13
|
+
autoload :StructDefinition, 'solargraph/convention/struct_definition'
|
13
14
|
|
14
15
|
# @type [Set<Convention::Base>]
|
15
16
|
@@conventions = Set.new
|
data/lib/solargraph/doc_map.rb
CHANGED
@@ -4,6 +4,8 @@ module Solargraph
|
|
4
4
|
# A collection of pins generated from required gems.
|
5
5
|
#
|
6
6
|
class DocMap
|
7
|
+
include Logging
|
8
|
+
|
7
9
|
# @return [Array<String>]
|
8
10
|
attr_reader :requires
|
9
11
|
|
@@ -28,12 +30,12 @@ module Solargraph
|
|
28
30
|
|
29
31
|
# @return [Array<Gem::Specification>]
|
30
32
|
def gemspecs
|
31
|
-
@gemspecs ||=
|
33
|
+
@gemspecs ||= required_gems_map.values.compact.flatten
|
32
34
|
end
|
33
35
|
|
34
36
|
# @return [Array<String>]
|
35
37
|
def unresolved_requires
|
36
|
-
@unresolved_requires ||=
|
38
|
+
@unresolved_requires ||= required_gems_map.select { |_, gemspecs| gemspecs.nil? }.keys
|
37
39
|
end
|
38
40
|
|
39
41
|
# @return [Hash{Gem::Specification => Array[Pin::Base]}]
|
@@ -52,20 +54,22 @@ module Solargraph
|
|
52
54
|
def generate
|
53
55
|
@pins = []
|
54
56
|
@uncached_gemspecs = []
|
55
|
-
|
56
|
-
if
|
57
|
-
try_cache gemspec
|
58
|
-
else
|
57
|
+
required_gems_map.each do |path, gemspecs|
|
58
|
+
if gemspecs.nil?
|
59
59
|
try_stdlib_map path
|
60
|
+
else
|
61
|
+
gemspecs.each do |gemspec|
|
62
|
+
try_cache gemspec
|
63
|
+
end
|
60
64
|
end
|
61
65
|
end
|
62
66
|
dependencies.each { |dep| try_cache dep }
|
63
67
|
@uncached_gemspecs.uniq!
|
64
68
|
end
|
65
69
|
|
66
|
-
# @return [Hash{String => Gem::Specification
|
67
|
-
def
|
68
|
-
@
|
70
|
+
# @return [Hash{String => Array<Gem::Specification>}]
|
71
|
+
def required_gems_map
|
72
|
+
@required_gems_map ||= requires.to_h { |path| [path, resolve_path_to_gemspecs(path)] }
|
69
73
|
end
|
70
74
|
|
71
75
|
# @return [Hash{String => Gem::Specification}]
|
@@ -125,10 +129,26 @@ module Solargraph
|
|
125
129
|
end
|
126
130
|
|
127
131
|
# @param path [String]
|
128
|
-
# @return [Gem::Specification
|
129
|
-
def
|
132
|
+
# @return [::Array<Gem::Specification>, nil]
|
133
|
+
def resolve_path_to_gemspecs path
|
130
134
|
return nil if path.empty?
|
131
135
|
|
136
|
+
if path == 'bundler/require'
|
137
|
+
# find only the gems bundler is now using
|
138
|
+
gemspecs = Bundler.definition.locked_gems.specs.flat_map do |lazy_spec|
|
139
|
+
logger.info "Handling #{lazy_spec.name}:#{lazy_spec.version} from #{path}"
|
140
|
+
[Gem::Specification.find_by_name(lazy_spec.name, lazy_spec.version)]
|
141
|
+
rescue Gem::MissingSpecError => e
|
142
|
+
logger.info("Could not find #{lazy_spec.name}:#{lazy_spec.version} with find_by_name, falling back to guess")
|
143
|
+
# can happen in local filesystem references
|
144
|
+
specs = resolve_path_to_gemspecs lazy_spec.name
|
145
|
+
logger.info "Gem #{lazy_spec.name} #{lazy_spec.version} from bundle not found: #{e}" if specs.nil?
|
146
|
+
next specs
|
147
|
+
end.compact
|
148
|
+
|
149
|
+
return gemspecs
|
150
|
+
end
|
151
|
+
|
132
152
|
gemspec = Gem::Specification.find_by_path(path)
|
133
153
|
if gemspec.nil?
|
134
154
|
gem_name_guess = path.split('/').first
|
@@ -142,16 +162,17 @@ module Solargraph
|
|
142
162
|
gemspec = potential_gemspec if potential_gemspec.files.any? { |gemspec_file| file == gemspec_file }
|
143
163
|
rescue Gem::MissingSpecError
|
144
164
|
Solargraph.logger.debug "Require path #{path} could not be resolved to a gem via find_by_path or guess of #{gem_name_guess}"
|
145
|
-
|
165
|
+
[]
|
146
166
|
end
|
147
167
|
end
|
148
|
-
|
168
|
+
return nil if gemspec.nil?
|
169
|
+
[gemspec_or_preference(gemspec)]
|
149
170
|
end
|
150
171
|
|
151
|
-
# @param gemspec [Gem::Specification
|
152
|
-
# @return [Gem::Specification
|
172
|
+
# @param gemspec [Gem::Specification]
|
173
|
+
# @return [Gem::Specification]
|
153
174
|
def gemspec_or_preference gemspec
|
154
|
-
return gemspec unless
|
175
|
+
return gemspec unless preference_map.key?(gemspec.name)
|
155
176
|
return gemspec if gemspec.version == preference_map[gemspec.name].version
|
156
177
|
|
157
178
|
change_gemspec_version gemspec, preference_map[by_path.name].version
|
@@ -170,12 +191,15 @@ module Solargraph
|
|
170
191
|
# @param gemspec [Gem::Specification]
|
171
192
|
# @return [Array<Gem::Specification>]
|
172
193
|
def fetch_dependencies gemspec
|
194
|
+
# @param spec [Gem::Dependency]
|
173
195
|
only_runtime_dependencies(gemspec).each_with_object(Set.new) do |spec, deps|
|
174
196
|
Solargraph.logger.info "Adding #{spec.name} dependency for #{gemspec.name}"
|
175
|
-
dep = Gem
|
197
|
+
dep = Gem.loaded_specs[spec.name]
|
198
|
+
# @todo is next line necessary?
|
199
|
+
dep ||= Gem::Specification.find_by_name(spec.name, spec.requirement)
|
176
200
|
deps.merge fetch_dependencies(dep) if deps.add?(dep)
|
177
201
|
rescue Gem::MissingSpecError
|
178
|
-
Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.
|
202
|
+
Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirement} for #{gemspec.name} not found."
|
179
203
|
end.to_a
|
180
204
|
end
|
181
205
|
|
@@ -7,9 +7,16 @@ module Solargraph
|
|
7
7
|
#
|
8
8
|
class MessageWorker
|
9
9
|
UPDATE_METHODS = [
|
10
|
-
'textDocument/didOpen',
|
11
10
|
'textDocument/didChange',
|
12
|
-
'
|
11
|
+
'textDocument/didClose',
|
12
|
+
'textDocument/didOpen',
|
13
|
+
'textDocument/didSave',
|
14
|
+
'workspace/didChangeConfiguration',
|
15
|
+
'workspace/didChangeWatchedFiles',
|
16
|
+
'workspace/didCreateFiles',
|
17
|
+
'workspace/didChangeWorkspaceFolders',
|
18
|
+
'workspace/didDeleteFiles',
|
19
|
+
'workspace/didRenameFiles'
|
13
20
|
].freeze
|
14
21
|
|
15
22
|
# @param host [Host]
|
@@ -84,11 +91,7 @@ module Solargraph
|
|
84
91
|
idx = messages.find_index do |msg|
|
85
92
|
UPDATE_METHODS.include?(msg['method']) || version_dependent?(msg)
|
86
93
|
end
|
87
|
-
|
88
|
-
|
89
|
-
msg = messages[idx]
|
90
|
-
messages.delete_at idx
|
91
|
-
msg
|
94
|
+
idx ? messages.delete_at(idx) : messages.shift
|
92
95
|
end
|
93
96
|
|
94
97
|
# True if the message requires a previous update to have executed in
|
@@ -749,6 +749,7 @@ module Solargraph
|
|
749
749
|
return change if source.code.length + 1 != change['text'].length
|
750
750
|
diffs = Diff::LCS.diff(source.code, change['text'])
|
751
751
|
return change if diffs.length.zero? || diffs.length > 1 || diffs.first.length > 1
|
752
|
+
# @sg-ignore push this upstream
|
752
753
|
# @type [Diff::LCS::Change]
|
753
754
|
diff = diffs.first.first
|
754
755
|
return change unless diff.adding? && ['.', ':', '(', ',', ' '].include?(diff.element)
|
data/lib/solargraph/library.rb
CHANGED
@@ -430,7 +430,8 @@ module Solargraph
|
|
430
430
|
Bench.new(
|
431
431
|
source_maps: source_map_hash.values,
|
432
432
|
workspace: workspace,
|
433
|
-
external_requires: external_requires
|
433
|
+
external_requires: external_requires,
|
434
|
+
live_map: @current ? source_map_hash[@current.filename] : nil
|
434
435
|
)
|
435
436
|
end
|
436
437
|
|
data/lib/solargraph/location.rb
CHANGED
@@ -30,6 +30,14 @@ module Solargraph
|
|
30
30
|
range.contain?(location.range.start) && range.contain?(location.range.ending) && filename == location.filename
|
31
31
|
end
|
32
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
|
+
|
33
41
|
# @return [Hash]
|
34
42
|
def to_hash
|
35
43
|
{
|
@@ -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
|
@@ -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
|
@@ -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
|
|