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.
Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/plugins.yml +2 -0
  3. data/.github/workflows/typecheck.yml +3 -1
  4. data/.gitignore +2 -0
  5. data/CHANGELOG.md +62 -0
  6. data/README.md +13 -3
  7. data/lib/solargraph/api_map/index.rb +24 -16
  8. data/lib/solargraph/api_map/store.rb +48 -23
  9. data/lib/solargraph/api_map.rb +175 -77
  10. data/lib/solargraph/bench.rb +17 -1
  11. data/lib/solargraph/complex_type/type_methods.rb +6 -1
  12. data/lib/solargraph/complex_type/unique_type.rb +98 -9
  13. data/lib/solargraph/complex_type.rb +35 -6
  14. data/lib/solargraph/convention/base.rb +3 -3
  15. data/lib/solargraph/convention/data_definition/data_assignment_node.rb +60 -0
  16. data/lib/solargraph/convention/data_definition/data_definition_node.rb +89 -0
  17. data/lib/solargraph/convention/data_definition.rb +104 -0
  18. data/lib/solargraph/convention/gemspec.rb +2 -1
  19. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +60 -0
  20. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +100 -0
  21. data/lib/solargraph/convention/struct_definition.rb +141 -0
  22. data/lib/solargraph/convention.rb +5 -3
  23. data/lib/solargraph/doc_map.rb +277 -57
  24. data/lib/solargraph/gem_pins.rb +53 -37
  25. data/lib/solargraph/language_server/host/message_worker.rb +10 -7
  26. data/lib/solargraph/language_server/host.rb +12 -2
  27. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +2 -0
  28. data/lib/solargraph/language_server/message/extended/document.rb +5 -2
  29. data/lib/solargraph/language_server/message/extended/document_gems.rb +3 -3
  30. data/lib/solargraph/library.rb +45 -17
  31. data/lib/solargraph/location.rb +21 -0
  32. data/lib/solargraph/logging.rb +1 -0
  33. data/lib/solargraph/parser/comment_ripper.rb +12 -6
  34. data/lib/solargraph/parser/flow_sensitive_typing.rb +227 -0
  35. data/lib/solargraph/parser/node_methods.rb +14 -0
  36. data/lib/solargraph/parser/node_processor/base.rb +9 -4
  37. data/lib/solargraph/parser/node_processor.rb +21 -8
  38. data/lib/solargraph/parser/parser_gem/class_methods.rb +16 -14
  39. data/lib/solargraph/parser/parser_gem/node_chainer.rb +10 -10
  40. data/lib/solargraph/parser/parser_gem/node_methods.rb +4 -2
  41. data/lib/solargraph/parser/parser_gem/node_processors/alias_node.rb +2 -1
  42. data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +21 -0
  43. data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +4 -2
  44. data/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +4 -2
  45. data/lib/solargraph/parser/parser_gem/node_processors/casgn_node.rb +2 -1
  46. data/lib/solargraph/parser/parser_gem/node_processors/cvasgn_node.rb +2 -1
  47. data/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +6 -3
  48. data/lib/solargraph/parser/parser_gem/node_processors/defs_node.rb +2 -1
  49. data/lib/solargraph/parser/parser_gem/node_processors/gvasgn_node.rb +2 -1
  50. data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +21 -0
  51. data/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +4 -2
  52. data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +2 -1
  53. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +4 -1
  54. data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +8 -7
  55. data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +42 -0
  56. data/lib/solargraph/parser/parser_gem/node_processors/orasgn_node.rb +1 -0
  57. data/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb +3 -1
  58. data/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +4 -3
  59. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +28 -16
  60. data/lib/solargraph/parser/parser_gem/node_processors/sym_node.rb +3 -1
  61. data/lib/solargraph/parser/parser_gem/node_processors/until_node.rb +29 -0
  62. data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +29 -0
  63. data/lib/solargraph/parser/parser_gem/node_processors.rb +14 -0
  64. data/lib/solargraph/parser/region.rb +1 -1
  65. data/lib/solargraph/parser.rb +1 -0
  66. data/lib/solargraph/pin/base.rb +316 -28
  67. data/lib/solargraph/pin/base_variable.rb +16 -9
  68. data/lib/solargraph/pin/block.rb +2 -0
  69. data/lib/solargraph/pin/breakable.rb +9 -0
  70. data/lib/solargraph/pin/callable.rb +74 -3
  71. data/lib/solargraph/pin/closure.rb +18 -1
  72. data/lib/solargraph/pin/common.rb +5 -0
  73. data/lib/solargraph/pin/delegated_method.rb +20 -1
  74. data/lib/solargraph/pin/documenting.rb +16 -0
  75. data/lib/solargraph/pin/keyword.rb +7 -2
  76. data/lib/solargraph/pin/local_variable.rb +15 -6
  77. data/lib/solargraph/pin/method.rb +169 -43
  78. data/lib/solargraph/pin/namespace.rb +17 -9
  79. data/lib/solargraph/pin/parameter.rb +60 -11
  80. data/lib/solargraph/pin/proxy_type.rb +12 -6
  81. data/lib/solargraph/pin/reference/override.rb +10 -6
  82. data/lib/solargraph/pin/reference/require.rb +2 -2
  83. data/lib/solargraph/pin/signature.rb +42 -0
  84. data/lib/solargraph/pin/singleton.rb +1 -1
  85. data/lib/solargraph/pin/symbol.rb +3 -2
  86. data/lib/solargraph/pin/until.rb +18 -0
  87. data/lib/solargraph/pin/while.rb +18 -0
  88. data/lib/solargraph/pin.rb +4 -1
  89. data/lib/solargraph/pin_cache.rb +185 -0
  90. data/lib/solargraph/position.rb +9 -0
  91. data/lib/solargraph/range.rb +9 -0
  92. data/lib/solargraph/rbs_map/conversions.rb +221 -67
  93. data/lib/solargraph/rbs_map/core_fills.rb +32 -16
  94. data/lib/solargraph/rbs_map/core_map.rb +34 -11
  95. data/lib/solargraph/rbs_map/stdlib_map.rb +15 -5
  96. data/lib/solargraph/rbs_map.rb +74 -17
  97. data/lib/solargraph/shell.rb +17 -18
  98. data/lib/solargraph/source/chain/array.rb +11 -7
  99. data/lib/solargraph/source/chain/block_symbol.rb +1 -1
  100. data/lib/solargraph/source/chain/block_variable.rb +1 -1
  101. data/lib/solargraph/source/chain/call.rb +53 -23
  102. data/lib/solargraph/source/chain/constant.rb +1 -1
  103. data/lib/solargraph/source/chain/hash.rb +4 -3
  104. data/lib/solargraph/source/chain/head.rb +1 -1
  105. data/lib/solargraph/source/chain/if.rb +1 -1
  106. data/lib/solargraph/source/chain/link.rb +2 -0
  107. data/lib/solargraph/source/chain/literal.rb +22 -2
  108. data/lib/solargraph/source/chain/or.rb +1 -1
  109. data/lib/solargraph/source/chain/z_super.rb +1 -1
  110. data/lib/solargraph/source/chain.rb +78 -48
  111. data/lib/solargraph/source/source_chainer.rb +2 -2
  112. data/lib/solargraph/source_map/clip.rb +3 -1
  113. data/lib/solargraph/source_map/mapper.rb +9 -5
  114. data/lib/solargraph/source_map.rb +0 -17
  115. data/lib/solargraph/type_checker/checks.rb +4 -0
  116. data/lib/solargraph/type_checker.rb +35 -8
  117. data/lib/solargraph/version.rb +1 -1
  118. data/lib/solargraph/views/_method.erb +10 -10
  119. data/lib/solargraph/views/_namespace.erb +3 -3
  120. data/lib/solargraph/views/document.erb +10 -10
  121. data/lib/solargraph/workspace/config.rb +1 -1
  122. data/lib/solargraph/workspace.rb +23 -5
  123. data/lib/solargraph/yard_map/helpers.rb +29 -1
  124. data/lib/solargraph/yard_map/mapper/to_constant.rb +7 -5
  125. data/lib/solargraph/yard_map/mapper/to_method.rb +53 -18
  126. data/lib/solargraph/yard_map/mapper/to_namespace.rb +9 -7
  127. data/lib/solargraph/yard_map/mapper.rb +4 -3
  128. data/lib/solargraph/yard_map/to_method.rb +4 -2
  129. data/lib/solargraph/yardoc.rb +7 -8
  130. data/lib/solargraph.rb +32 -1
  131. data/rbs/fills/tuple.rbs +150 -0
  132. data/rbs_collection.yaml +19 -0
  133. data/solargraph.gemspec +2 -1
  134. metadata +37 -9
  135. data/lib/solargraph/cache.rb +0 -77
@@ -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.document query }
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
- spec = api_map.uncached_gemspecs.find { |spec| !cache_errors.include?(spec) }
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
- logger.info "Caching #{spec.name} #{spec.version}"
594
- Thread.new do
595
- cache_pid = Process.spawn(workspace.command_path, 'cache', spec.name, spec.version.to_s)
596
- report_cache_progress spec.name, pending
597
- Process.wait(cache_pid)
598
- logger.info "Cached #{spec.name} #{spec.version}"
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.uncached_gemspecs.length} uncached gemspecs"
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
@@ -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
  {
@@ -13,6 +13,7 @@ module Solargraph
13
13
  }
14
14
 
15
15
  @@logger = Logger.new(STDERR, level: DEFAULT_LOG_LEVEL)
16
+ # @sg-ignore Fix cvar issue
16
17
  @@logger.formatter = proc do |severity, datetime, progname, msg|
17
18
  "[#{severity}] #{msg}\n"
18
19
  end
@@ -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
- 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)
38
+ create_snippet(result)
31
39
  result
32
40
  end
33
41
 
34
42
  def on_embdoc *args
35
43
  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)
44
+ create_snippet(result)
38
45
  result
39
46
  end
40
47
 
41
48
  def on_embdoc_end *args
42
49
  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)
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 [void]
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{|pin| pin.is_a?(Pin::Closure) && pin.path && !pin.path.empty? && pin.location.range.contain?(position)}.last
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] = cls
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
- klass = @@processors[node.type] || NodeProcessor::Base
39
- processor = klass.new(node, region, pins, locals)
40
- processor.process
41
- [processor.pins, processor.locals]
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 'parser/current'
4
- require 'parser/source/buffer'
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
- buffer = ::Parser::Source::Buffer.new(filename, 0)
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
- # @todo Consider setting an instance variable. We might not need to
38
- # recreate the parser every time we use it.
39
- parser = ::Parser::CurrentRuby.new(FlawedBuilder.new)
40
- parser.diagnostics.all_errors_are_fatal = true
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
- 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