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.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +58 -0
  3. data/lib/solargraph/api_map/cache.rb +10 -1
  4. data/lib/solargraph/api_map/index.rb +167 -0
  5. data/lib/solargraph/api_map/store.rb +75 -122
  6. data/lib/solargraph/api_map.rb +109 -41
  7. data/lib/solargraph/bench.rb +17 -1
  8. data/lib/solargraph/complex_type/type_methods.rb +17 -11
  9. data/lib/solargraph/complex_type/unique_type.rb +181 -17
  10. data/lib/solargraph/complex_type.rb +103 -24
  11. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +51 -0
  12. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +100 -0
  13. data/lib/solargraph/convention/struct_definition.rb +101 -0
  14. data/lib/solargraph/convention.rb +2 -0
  15. data/lib/solargraph/doc_map.rb +43 -18
  16. data/lib/solargraph/equality.rb +33 -0
  17. data/lib/solargraph/language_server/host/message_worker.rb +51 -5
  18. data/lib/solargraph/language_server/host.rb +13 -11
  19. data/lib/solargraph/language_server/message/base.rb +19 -12
  20. data/lib/solargraph/language_server/message/initialize.rb +3 -1
  21. data/lib/solargraph/language_server/message/text_document/completion.rb +0 -3
  22. data/lib/solargraph/language_server/message/text_document/definition.rb +3 -3
  23. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +3 -3
  24. data/lib/solargraph/language_server/message/text_document/formatting.rb +4 -0
  25. data/lib/solargraph/language_server/message/text_document/hover.rb +1 -1
  26. data/lib/solargraph/language_server/message/text_document/type_definition.rb +3 -3
  27. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +2 -2
  28. data/lib/solargraph/language_server/progress.rb +19 -2
  29. data/lib/solargraph/library.rb +31 -41
  30. data/lib/solargraph/location.rb +29 -1
  31. data/lib/solargraph/parser/comment_ripper.rb +11 -6
  32. data/lib/solargraph/parser/flow_sensitive_typing.rb +226 -0
  33. data/lib/solargraph/parser/node_methods.rb +15 -1
  34. data/lib/solargraph/parser/parser_gem/class_methods.rb +11 -6
  35. data/lib/solargraph/parser/parser_gem/node_chainer.rb +10 -10
  36. data/lib/solargraph/parser/parser_gem/node_methods.rb +6 -4
  37. data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +21 -0
  38. data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +23 -19
  39. data/lib/solargraph/parser/parser_gem/node_processors/casgn_node.rb +21 -1
  40. data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +21 -0
  41. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +8 -2
  42. data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +26 -5
  43. data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +41 -0
  44. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +1 -1
  45. data/lib/solargraph/parser/parser_gem/node_processors/until_node.rb +28 -0
  46. data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +28 -0
  47. data/lib/solargraph/parser/parser_gem/node_processors.rb +10 -0
  48. data/lib/solargraph/parser.rb +3 -5
  49. data/lib/solargraph/pin/base.rb +47 -17
  50. data/lib/solargraph/pin/base_variable.rb +11 -4
  51. data/lib/solargraph/pin/block.rb +8 -26
  52. data/lib/solargraph/pin/breakable.rb +9 -0
  53. data/lib/solargraph/pin/callable.rb +147 -0
  54. data/lib/solargraph/pin/closure.rb +8 -3
  55. data/lib/solargraph/pin/common.rb +2 -6
  56. data/lib/solargraph/pin/conversions.rb +3 -2
  57. data/lib/solargraph/pin/instance_variable.rb +2 -2
  58. data/lib/solargraph/pin/local_variable.rb +7 -1
  59. data/lib/solargraph/pin/method.rb +76 -48
  60. data/lib/solargraph/pin/namespace.rb +14 -11
  61. data/lib/solargraph/pin/parameter.rb +24 -17
  62. data/lib/solargraph/pin/proxy_type.rb +13 -7
  63. data/lib/solargraph/pin/signature.rb +3 -129
  64. data/lib/solargraph/pin/until.rb +18 -0
  65. data/lib/solargraph/pin/while.rb +18 -0
  66. data/lib/solargraph/pin.rb +7 -1
  67. data/lib/solargraph/position.rb +7 -0
  68. data/lib/solargraph/range.rb +9 -4
  69. data/lib/solargraph/rbs_map/conversions.rb +84 -45
  70. data/lib/solargraph/rbs_map/core_fills.rb +16 -9
  71. data/lib/solargraph/rbs_map.rb +1 -0
  72. data/lib/solargraph/shell.rb +19 -2
  73. data/lib/solargraph/source/chain/array.rb +10 -8
  74. data/lib/solargraph/source/chain/block_symbol.rb +1 -1
  75. data/lib/solargraph/source/chain/block_variable.rb +1 -1
  76. data/lib/solargraph/source/chain/call.rb +125 -61
  77. data/lib/solargraph/source/chain/constant.rb +1 -1
  78. data/lib/solargraph/source/chain/hash.rb +8 -2
  79. data/lib/solargraph/source/chain/if.rb +5 -0
  80. data/lib/solargraph/source/chain/link.rb +28 -5
  81. data/lib/solargraph/source/chain/literal.rb +27 -2
  82. data/lib/solargraph/source/chain/or.rb +1 -1
  83. data/lib/solargraph/source/chain/z_super.rb +1 -1
  84. data/lib/solargraph/source/chain.rb +131 -63
  85. data/lib/solargraph/source/cursor.rb +3 -2
  86. data/lib/solargraph/source/source_chainer.rb +2 -2
  87. data/lib/solargraph/source.rb +104 -86
  88. data/lib/solargraph/source_map/clip.rb +8 -6
  89. data/lib/solargraph/source_map/data.rb +30 -0
  90. data/lib/solargraph/source_map.rb +28 -16
  91. data/lib/solargraph/type_checker/checks.rb +4 -0
  92. data/lib/solargraph/type_checker/rules.rb +6 -1
  93. data/lib/solargraph/type_checker.rb +48 -21
  94. data/lib/solargraph/version.rb +1 -1
  95. data/lib/solargraph/views/environment.erb +3 -5
  96. data/lib/solargraph/workspace/config.rb +7 -3
  97. data/lib/solargraph/workspace.rb +1 -1
  98. data/lib/solargraph/yard_map/mapper/to_constant.rb +1 -0
  99. data/lib/solargraph/yard_map/mapper/to_method.rb +42 -15
  100. data/lib/solargraph/yard_map/mapper/to_namespace.rb +1 -0
  101. data/lib/solargraph/yard_map/mapper.rb +1 -0
  102. data/lib/solargraph/yardoc.rb +1 -1
  103. data/lib/solargraph.rb +1 -0
  104. data/solargraph.gemspec +5 -5
  105. metadata +41 -25
@@ -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
- !mutex.locked?
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 { api_map.clip(cursor).define.map { |pin| pin.realize(api_map) } }
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::Range>]
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
- @threads.delete_if(&:stop?)
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(source_map_hash[src.filename])
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
- return if source_map_hash[source.filename].code == source.code &&
584
- source_map_hash[source.filename].source.synchronized? &&
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
- @threads.delete_if(&:stop?)
670
- .last&.join
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
@@ -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 and range.
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
- 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
@@ -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
- buffer = ::Parser::Source::Buffer.new(filename, 0)
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<AST::Node>]
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
- 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
@@ -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, :or_asgn]
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, BaseObject>]
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 = []