solargraph 0.54.5 → 0.55.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/lib/solargraph/api_map/store.rb +3 -1
  4. data/lib/solargraph/api_map.rb +18 -8
  5. data/lib/solargraph/complex_type/unique_type.rb +88 -7
  6. data/lib/solargraph/complex_type.rb +35 -6
  7. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +60 -0
  8. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +100 -0
  9. data/lib/solargraph/convention/struct_definition.rb +101 -0
  10. data/lib/solargraph/convention.rb +1 -0
  11. data/lib/solargraph/doc_map.rb +49 -18
  12. data/lib/solargraph/language_server/host/message_worker.rb +10 -7
  13. data/lib/solargraph/language_server/host.rb +1 -0
  14. data/lib/solargraph/location.rb +8 -0
  15. data/lib/solargraph/parser/comment_ripper.rb +11 -6
  16. data/lib/solargraph/parser/flow_sensitive_typing.rb +226 -0
  17. data/lib/solargraph/parser/node_methods.rb +14 -0
  18. data/lib/solargraph/parser/node_processor.rb +0 -1
  19. data/lib/solargraph/parser/parser_gem/class_methods.rb +9 -0
  20. data/lib/solargraph/parser/parser_gem/node_chainer.rb +10 -10
  21. data/lib/solargraph/parser/parser_gem/node_methods.rb +3 -1
  22. data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +21 -0
  23. data/lib/solargraph/parser/parser_gem/node_processors/casgn_node.rb +21 -1
  24. data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +21 -0
  25. data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +26 -5
  26. data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +41 -0
  27. data/lib/solargraph/parser/parser_gem/node_processors/until_node.rb +28 -0
  28. data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +28 -0
  29. data/lib/solargraph/parser/parser_gem/node_processors.rb +10 -0
  30. data/lib/solargraph/parser.rb +1 -0
  31. data/lib/solargraph/pin/base.rb +9 -3
  32. data/lib/solargraph/pin/base_variable.rb +7 -1
  33. data/lib/solargraph/pin/block.rb +2 -0
  34. data/lib/solargraph/pin/breakable.rb +9 -0
  35. data/lib/solargraph/pin/delegated_method.rb +18 -1
  36. data/lib/solargraph/pin/local_variable.rb +7 -1
  37. data/lib/solargraph/pin/method.rb +20 -18
  38. data/lib/solargraph/pin/namespace.rb +10 -7
  39. data/lib/solargraph/pin/parameter.rb +13 -5
  40. data/lib/solargraph/pin/proxy_type.rb +12 -6
  41. data/lib/solargraph/pin/until.rb +18 -0
  42. data/lib/solargraph/pin/while.rb +18 -0
  43. data/lib/solargraph/pin.rb +3 -0
  44. data/lib/solargraph/rbs_map/conversions.rb +8 -8
  45. data/lib/solargraph/rbs_map/core_fills.rb +10 -3
  46. data/lib/solargraph/source/chain/array.rb +4 -3
  47. data/lib/solargraph/source/chain/call.rb +46 -17
  48. data/lib/solargraph/source/chain/constant.rb +1 -1
  49. data/lib/solargraph/source/chain/hash.rb +3 -2
  50. data/lib/solargraph/source/chain/link.rb +2 -0
  51. data/lib/solargraph/source/chain/literal.rb +22 -2
  52. data/lib/solargraph/source/chain/z_super.rb +1 -1
  53. data/lib/solargraph/source/chain.rb +77 -47
  54. data/lib/solargraph/source/source_chainer.rb +2 -2
  55. data/lib/solargraph/source_map/clip.rb +3 -1
  56. data/lib/solargraph/type_checker/checks.rb +4 -0
  57. data/lib/solargraph/type_checker.rb +35 -8
  58. data/lib/solargraph/version.rb +1 -1
  59. data/lib/solargraph/yard_map/mapper/to_method.rb +42 -15
  60. metadata +14 -2
@@ -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 ||= required_gem_map.values.compact
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 ||= required_gem_map.select { |_, gemspec| gemspec.nil? }.keys
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
- required_gem_map.each do |path, gemspec|
56
- if gemspec
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, nil}]
67
- def required_gem_map
68
- @required_gem_map ||= requires.to_h { |path| [path, resolve_path_to_gemspec(path)] }
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,33 @@ module Solargraph
125
129
  end
126
130
 
127
131
  # @param path [String]
128
- # @return [Gem::Specification, nil]
129
- def resolve_path_to_gemspec path
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
+ # @todo Quick fix for cases when Solargraph is running without Bundler.
138
+ # The next goal is to enable loading of external bundles, i.e.,
139
+ # finding gems that are defined in the workspace's bundle when
140
+ # Solargraph is running in a different environment.
141
+ # See https://github.com/castwide/vscode-solargraph/issues/279
142
+ require 'bundler'
143
+
144
+ # find only the gems bundler is now using
145
+ gemspecs = Bundler.definition.locked_gems.specs.flat_map do |lazy_spec|
146
+ logger.info "Handling #{lazy_spec.name}:#{lazy_spec.version} from #{path}"
147
+ [Gem::Specification.find_by_name(lazy_spec.name, lazy_spec.version)]
148
+ rescue Gem::MissingSpecError => e
149
+ logger.info("Could not find #{lazy_spec.name}:#{lazy_spec.version} with find_by_name, falling back to guess")
150
+ # can happen in local filesystem references
151
+ specs = resolve_path_to_gemspecs lazy_spec.name
152
+ logger.info "Gem #{lazy_spec.name} #{lazy_spec.version} from bundle not found: #{e}" if specs.nil?
153
+ next specs
154
+ end.compact
155
+
156
+ return gemspecs
157
+ end
158
+
132
159
  gemspec = Gem::Specification.find_by_path(path)
133
160
  if gemspec.nil?
134
161
  gem_name_guess = path.split('/').first
@@ -142,16 +169,17 @@ module Solargraph
142
169
  gemspec = potential_gemspec if potential_gemspec.files.any? { |gemspec_file| file == gemspec_file }
143
170
  rescue Gem::MissingSpecError
144
171
  Solargraph.logger.debug "Require path #{path} could not be resolved to a gem via find_by_path or guess of #{gem_name_guess}"
145
- nil
172
+ []
146
173
  end
147
174
  end
148
- gemspec_or_preference gemspec
175
+ return nil if gemspec.nil?
176
+ [gemspec_or_preference(gemspec)]
149
177
  end
150
178
 
151
- # @param gemspec [Gem::Specification, nil]
152
- # @return [Gem::Specification, nil]
179
+ # @param gemspec [Gem::Specification]
180
+ # @return [Gem::Specification]
153
181
  def gemspec_or_preference gemspec
154
- return gemspec unless gemspec && preference_map.key?(gemspec.name)
182
+ return gemspec unless preference_map.key?(gemspec.name)
155
183
  return gemspec if gemspec.version == preference_map[gemspec.name].version
156
184
 
157
185
  change_gemspec_version gemspec, preference_map[by_path.name].version
@@ -170,12 +198,15 @@ module Solargraph
170
198
  # @param gemspec [Gem::Specification]
171
199
  # @return [Array<Gem::Specification>]
172
200
  def fetch_dependencies gemspec
201
+ # @param spec [Gem::Dependency]
173
202
  only_runtime_dependencies(gemspec).each_with_object(Set.new) do |spec, deps|
174
203
  Solargraph.logger.info "Adding #{spec.name} dependency for #{gemspec.name}"
175
- dep = Gem::Specification.find_by_name(spec.name, spec.requirement)
204
+ dep = Gem.loaded_specs[spec.name]
205
+ # @todo is next line necessary?
206
+ dep ||= Gem::Specification.find_by_name(spec.name, spec.requirement)
176
207
  deps.merge fetch_dependencies(dep) if deps.add?(dep)
177
208
  rescue Gem::MissingSpecError
178
- Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirements} for #{gemspec.name} not found."
209
+ Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirement} for #{gemspec.name} not found."
179
210
  end.to_a
180
211
  end
181
212
 
@@ -7,9 +7,16 @@ module Solargraph
7
7
  #
8
8
  class MessageWorker
9
9
  UPDATE_METHODS = [
10
- 'textDocument/didOpen',
11
10
  'textDocument/didChange',
12
- 'workspace/didChangeWatchedFiles'
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
- return messages.shift unless idx
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)
@@ -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
- chomped = result[1].chomp
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
- chomped = result[1].chomp
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
- chomped = result[1].chomp
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
@@ -9,7 +9,6 @@ module Solargraph
9
9
  autoload :Base, 'solargraph/parser/node_processor/base'
10
10
 
11
11
  class << self
12
- # @type [Hash<Symbol, Class<NodeProcessor::Base>>]
13
12
  @@processors ||= {}
14
13
 
15
14
  # Register a processor for a node type.
@@ -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
@@ -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
@@ -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
- else
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
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Parser
5
+ module ParserGem
6
+ module NodeProcessors
7
+ class AndNode < Parser::NodeProcessor::Base
8
+ include ParserGem::NodeMethods
9
+
10
+ def process
11
+ process_children
12
+
13
+ position = get_node_start_position(node)
14
+ enclosing_breakable_pin = pins.select{|pin| pin.is_a?(Pin::Breakable) && pin.location.range.contain?(position)}.last
15
+ FlowSensitiveTyping.new(locals, enclosing_breakable_pin).process_and(node)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -8,6 +8,17 @@ module Solargraph
8
8
  include ParserGem::NodeMethods
9
9
 
10
10
  def process
11
+ if Convention::StructDefinition::StructAssignmentNode.valid?(node)
12
+ process_struct_assignment
13
+ else
14
+ process_constant_assignment
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ # @return [void]
21
+ def process_constant_assignment
11
22
  pins.push Solargraph::Pin::Constant.new(
12
23
  location: get_node_location(node),
13
24
  closure: region.closure,
@@ -18,7 +29,16 @@ module Solargraph
18
29
  process_children
19
30
  end
20
31
 
21
- private
32
+ # TODO: Move this out of [CasgnNode] once [Solargraph::Parser::NodeProcessor] supports
33
+ # multiple processors.
34
+ def process_struct_assignment
35
+ processor_klass = Convention::StructDefinition::NodeProcessors::StructNode
36
+ processor = processor_klass.new(node, region, pins, locals)
37
+ processor.process
38
+
39
+ @pins = processor.pins
40
+ @locals = processor.locals
41
+ end
22
42
 
23
43
  # @return [String]
24
44
  def const_name
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Parser
5
+ module ParserGem
6
+ module NodeProcessors
7
+ class IfNode < Parser::NodeProcessor::Base
8
+ include ParserGem::NodeMethods
9
+
10
+ def process
11
+ process_children
12
+
13
+ position = get_node_start_position(node)
14
+ enclosing_breakable_pin = pins.select{|pin| pin.is_a?(Pin::Breakable) && pin.location.range.contain?(position)}.last
15
+ FlowSensitiveTyping.new(locals, enclosing_breakable_pin).process_if(node)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end