solargraph 0.56.0 → 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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -1
  3. data/lib/solargraph/convention/data_definition/data_assignment_node.rb +60 -0
  4. data/lib/solargraph/convention/data_definition/data_definition_node.rb +89 -0
  5. data/lib/solargraph/convention/data_definition.rb +104 -0
  6. data/lib/solargraph/convention/gemspec.rb +2 -1
  7. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +1 -1
  8. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +1 -1
  9. data/lib/solargraph/convention/struct_definition.rb +60 -20
  10. data/lib/solargraph/convention.rb +1 -0
  11. data/lib/solargraph/doc_map.rb +8 -5
  12. data/lib/solargraph/library.rb +39 -15
  13. data/lib/solargraph/parser/node_processor/base.rb +9 -4
  14. data/lib/solargraph/parser/node_processor.rb +19 -7
  15. data/lib/solargraph/parser/parser_gem/node_processors/casgn_node.rb +1 -21
  16. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +4 -1
  17. data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +0 -22
  18. data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +1 -0
  19. data/lib/solargraph/parser/parser_gem/node_processors/orasgn_node.rb +1 -0
  20. data/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb +1 -0
  21. data/lib/solargraph/parser/parser_gem/node_processors/sym_node.rb +1 -0
  22. data/lib/solargraph/parser/parser_gem/node_processors.rb +4 -0
  23. data/lib/solargraph/pin/base.rb +15 -0
  24. data/lib/solargraph/pin/method.rb +4 -2
  25. data/lib/solargraph/rbs_map/conversions.rb +41 -14
  26. data/lib/solargraph/version.rb +1 -1
  27. data/lib/solargraph/workspace/config.rb +1 -1
  28. data/lib/solargraph/workspace.rb +8 -0
  29. data/lib/solargraph/yard_map/helpers.rb +29 -1
  30. data/lib/solargraph/yard_map/mapper/to_constant.rb +5 -5
  31. data/lib/solargraph/yard_map/mapper/to_method.rb +1 -6
  32. data/lib/solargraph/yard_map/mapper/to_namespace.rb +7 -7
  33. data/lib/solargraph/yardoc.rb +2 -0
  34. data/lib/solargraph.rb +13 -0
  35. data/solargraph.gemspec +1 -1
  36. metadata +7 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f8c351394fab745041193e5d33cd582f0131e6d0f2d30f210767bd273376f24
4
- data.tar.gz: 9d7a3c46740bd9fa040190d00f0e17cab638bde681da5998337dd41e6a924ff6
3
+ metadata.gz: 82754cd0869fed91a86a2bfb08ca0f54d6468f942620cddd3148bad99724ad88
4
+ data.tar.gz: 4e017a78936b0d24eaa6d5db7edcd01ea2b43d6cb365eb5b4892576a141ca2b7
5
5
  SHA512:
6
- metadata.gz: 0fefbe369014891f339463c19a198ab0b9c1d918fb70662b496d90f79871e7ab03d3b4e7bf853fee19f7bbd467817690b29e506ca340518156667e986685c92e
7
- data.tar.gz: e92af09e57129e47e6c4b685642460c514bc7c26a015f93c7eb81d917b12717cfac0621181fc36dcc481770ce6b3d93a611f5acd5aee64b1730c8dde4b63b949
6
+ metadata.gz: 304fb86181925f916d7119da44b85a3753fc33d961da8a61c5525dbcb8d814a87baeeb9767d17b7ec6548c3ffe725bfda6eafbfd7ea75e8bdddf3f1a1a5c103b
7
+ data.tar.gz: 73d437e753be70628008f09a7846cba5e16fd47d045017b41c0b301d8a5b34ca04a45127a37c6eada4e61287944ca1af659ecab8df4681bab42a8ecc37d73bc4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
+ ## 0.56.2 - July 29, 2025
2
+ - Add support for Ruby Data.define (#970)
3
+ - Ensure that pin locations are always populated (#965)
4
+ - Improve struct support (#992)
5
+ - Include Rakefile, Gemfile, and gemspec files in config by default (#984)
6
+ - Use Open3 to cache gemspecs (#1000)
7
+ - Eager load node processors (#1002)
8
+
9
+ ## 0.56.1 - July 13, 2025
10
+ - Library avoids blocking on pending yardoc caches (#990)
11
+ - DocMap checks for default Gemfile (#989)
12
+ - [Bug fix] Fixed an error in rbs/fills/tuple.rbs (#993)
13
+
1
14
  ## 0.56.0 - July 1, 2025
2
- - [regression] Gem caching perf and logging fixes #983
15
+ - [regression] Gem caching perf and logging fixes #983
3
16
 
4
17
  ## 0.55.5 - July 1, 2025
5
18
  - Flatten results of DocMap external bundle query (#981)
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Convention
5
+ module DataDefinition
6
+ # A node wrapper for a Data definition via const assignment.
7
+ # @example
8
+ # MyData = Data.new(:bar, :baz) do
9
+ # def foo
10
+ # end
11
+ # end
12
+ class DataAssignmentNode < DataDefintionNode
13
+ class << self
14
+ # @example
15
+ # s(:casgn, nil, :Foo,
16
+ # s(:block,
17
+ # s(:send,
18
+ # s(:const, nil, :Data), :define,
19
+ # s(:sym, :bar),
20
+ # s(:sym, :baz)),
21
+ # s(:args),
22
+ # s(:def, :foo,
23
+ # s(:args),
24
+ # s(:send, nil, :bar))))
25
+ def match?(node)
26
+ return false unless node&.type == :casgn
27
+ return false if node.children[2].nil?
28
+
29
+ data_node = if node.children[2].type == :block
30
+ node.children[2].children[0]
31
+ else
32
+ node.children[2]
33
+ end
34
+
35
+ data_definition_node?(data_node)
36
+ end
37
+ end
38
+
39
+ def class_name
40
+ if node.children[0]
41
+ Parser::NodeMethods.unpack_name(node.children[0]) + "::#{node.children[1]}"
42
+ else
43
+ node.children[1].to_s
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ # @return [Parser::AST::Node]
50
+ def data_node
51
+ if node.children[2].type == :block
52
+ node.children[2].children[0]
53
+ else
54
+ node.children[2]
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Convention
5
+ module DataDefinition
6
+ # A node wrapper for a Data definition via inheritance.
7
+ # @example
8
+ # class MyData < Data.new(:bar, :baz)
9
+ # def foo
10
+ # end
11
+ # end
12
+ class DataDefintionNode
13
+ class << self
14
+ # @example
15
+ # s(:class,
16
+ # s(:const, nil, :Foo),
17
+ # s(:send,
18
+ # s(:const, nil, :Data), :define,
19
+ # s(:sym, :bar),
20
+ # s(:sym, :baz)),
21
+ # s(:hash,
22
+ # s(:pair,
23
+ # s(:sym, :keyword_init),
24
+ # s(:true)))),
25
+ # s(:def, :foo,
26
+ # s(:args),
27
+ # s(:send, nil, :bar)))
28
+ def match?(node)
29
+ return false unless node&.type == :class
30
+
31
+ data_definition_node?(node.children[1])
32
+ end
33
+
34
+ private
35
+
36
+ # @param data_node [Parser::AST::Node]
37
+ # @return [Boolean]
38
+ def data_definition_node?(data_node)
39
+ return false unless data_node.is_a?(::Parser::AST::Node)
40
+ return false unless data_node&.type == :send
41
+ return false unless data_node.children[0]&.type == :const
42
+ return false unless data_node.children[0].children[1] == :Data
43
+ return false unless data_node.children[1] == :define
44
+
45
+ true
46
+ end
47
+ end
48
+
49
+ # @return [Parser::AST::Node]
50
+ def initialize(node)
51
+ @node = node
52
+ end
53
+
54
+ # @return [String]
55
+ def class_name
56
+ Parser::NodeMethods.unpack_name(node)
57
+ end
58
+
59
+ # @return [Array<Array(Parser::AST::Node, String)>]
60
+ def attributes
61
+ data_attribute_nodes.map do |data_def_param|
62
+ next unless data_def_param.type == :sym
63
+ [data_def_param, data_def_param.children[0].to_s]
64
+ end.compact
65
+ end
66
+
67
+ # @return [Parser::AST::Node]
68
+ def body_node
69
+ node.children[2]
70
+ end
71
+
72
+ private
73
+
74
+ # @return [Parser::AST::Node]
75
+ attr_reader :node
76
+
77
+ # @return [Parser::AST::Node]
78
+ def data_node
79
+ node.children[1]
80
+ end
81
+
82
+ # @return [Array<Parser::AST::Node>]
83
+ def data_attribute_nodes
84
+ data_node.children[2..-1]
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Convention
5
+ module DataDefinition
6
+ autoload :DataDefintionNode, 'solargraph/convention/data_definition/data_definition_node'
7
+ autoload :DataAssignmentNode, 'solargraph/convention/data_definition/data_assignment_node'
8
+
9
+ module NodeProcessors
10
+ class DataNode < Parser::NodeProcessor::Base
11
+ # @return [Boolean] continue processing the next processor of the same node.
12
+ def process
13
+ return true if data_definition_node.nil?
14
+
15
+ loc = get_node_location(node)
16
+ nspin = Solargraph::Pin::Namespace.new(
17
+ type: :class,
18
+ location: loc,
19
+ closure: region.closure,
20
+ name: data_definition_node.class_name,
21
+ comments: comments_for(node),
22
+ visibility: :public,
23
+ gates: region.closure.gates.freeze
24
+ )
25
+ pins.push nspin
26
+
27
+ # define initialize method
28
+ initialize_method_pin = Pin::Method.new(
29
+ name: 'initialize',
30
+ parameters: [],
31
+ scope: :instance,
32
+ location: get_node_location(node),
33
+ closure: nspin,
34
+ visibility: :private,
35
+ comments: comments_for(node)
36
+ )
37
+
38
+ # TODO: Support both arg and kwarg initializers for Data.define
39
+ # Solargraph::SourceMap::Clip#complete_keyword_parameters does not seem to currently take into account [Pin::Method#signatures] hence we only one for :kwarg
40
+ pins.push initialize_method_pin
41
+
42
+ data_definition_node.attributes.map do |attribute_node, attribute_name|
43
+ initialize_method_pin.parameters.push(
44
+ Pin::Parameter.new(
45
+ name: attribute_name,
46
+ decl: :kwarg,
47
+ location: get_node_location(attribute_node),
48
+ closure: initialize_method_pin
49
+ )
50
+ )
51
+ end
52
+
53
+ # define attribute readers and instance variables
54
+ data_definition_node.attributes.each do |attribute_node, attribute_name|
55
+ name = attribute_name.to_s
56
+ method_pin = Pin::Method.new(
57
+ name: name,
58
+ parameters: [],
59
+ scope: :instance,
60
+ location: get_node_location(attribute_node),
61
+ closure: nspin,
62
+ comments: attribute_comments(attribute_node, attribute_name),
63
+ visibility: :public
64
+ )
65
+
66
+ pins.push method_pin
67
+
68
+ pins.push Pin::InstanceVariable.new(name: "@#{attribute_name}",
69
+ closure: method_pin,
70
+ location: get_node_location(attribute_node),
71
+ comments: attribute_comments(attribute_node, attribute_name))
72
+ end
73
+
74
+ process_children region.update(closure: nspin, visibility: :public)
75
+
76
+ false
77
+ end
78
+
79
+ private
80
+
81
+ # @return [DataDefintionNode, nil]
82
+ def data_definition_node
83
+ @data_definition_node ||= if DataDefintionNode.match?(node)
84
+ DataDefintionNode.new(node)
85
+ elsif DataAssignmentNode.match?(node)
86
+ DataAssignmentNode.new(node)
87
+ end
88
+ end
89
+
90
+ # @param attribute_node [Parser::AST::Node]
91
+ # @return [String, nil]
92
+ def attribute_comments(attribute_node, attribute_name)
93
+ data_comments = comments_for(attribute_node)
94
+ return if data_comments.nil? || data_comments.empty?
95
+
96
+ data_comments.split("\n").find do |row|
97
+ row.include?(attribute_name)
98
+ end&.gsub('@param', '@return')&.gsub(attribute_name, '')
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -12,7 +12,8 @@ module Solargraph
12
12
  'Gem::Specification.new',
13
13
  %(
14
14
  @yieldparam [self]
15
- )
15
+ ),
16
+ source: :gemspec
16
17
  )
17
18
  ]
18
19
  )
@@ -22,7 +22,7 @@ module Solargraph
22
22
  # s(:def, :foo,
23
23
  # s(:args),
24
24
  # s(:send, nil, :bar))))
25
- def valid?(node)
25
+ def match?(node)
26
26
  return false unless node&.type == :casgn
27
27
  return false if node.children[2].nil?
28
28
 
@@ -25,7 +25,7 @@ module Solargraph
25
25
  # s(:def, :foo,
26
26
  # s(:args),
27
27
  # s(:send, nil, :bar)))
28
- def valid?(node)
28
+ def match?(node)
29
29
  return false unless node&.type == :class
30
30
 
31
31
  struct_definition_node?(node.children[1])
@@ -8,8 +8,9 @@ module Solargraph
8
8
 
9
9
  module NodeProcessors
10
10
  class StructNode < Parser::NodeProcessor::Base
11
+ # @return [Boolean] continue processing the next processor of the same node.
11
12
  def process
12
- return if struct_definition_node.nil?
13
+ return true if struct_definition_node.nil?
13
14
 
14
15
  loc = get_node_location(node)
15
16
  nspin = Solargraph::Pin::Namespace.new(
@@ -17,7 +18,7 @@ module Solargraph
17
18
  location: loc,
18
19
  closure: region.closure,
19
20
  name: struct_definition_node.class_name,
20
- comments: comments_for(node),
21
+ docstring: docstring,
21
22
  visibility: :public,
22
23
  gates: region.closure.gates.freeze
23
24
  )
@@ -31,7 +32,7 @@ module Solargraph
31
32
  location: get_node_location(node),
32
33
  closure: nspin,
33
34
  visibility: :private,
34
- comments: comments_for(node)
35
+ docstring: docstring
35
36
  )
36
37
 
37
38
  pins.push initialize_method_pin
@@ -50,49 +51,88 @@ module Solargraph
50
51
  # define attribute accessors and instance variables
51
52
  struct_definition_node.attributes.each do |attribute_node, attribute_name|
52
53
  [attribute_name, "#{attribute_name}="].each do |name|
54
+ docs = docstring.tags.find { |t| t.tag_name == 'param' && t.name == attribute_name }
55
+
53
56
  method_pin = Pin::Method.new(
54
57
  name: name,
55
58
  parameters: [],
56
59
  scope: :instance,
57
60
  location: get_node_location(attribute_node),
58
61
  closure: nspin,
59
- comments: attribute_comments(attribute_node, attribute_name),
62
+ # even assignments return the value
63
+ comments: attribute_comment(docs, false),
60
64
  visibility: :public
61
65
  )
62
66
 
63
- pins.push method_pin
67
+ if name.end_with?('=')
68
+ method_pin.parameters << Pin::Parameter.new(
69
+ name: attribute_name,
70
+ location: get_node_location(attribute_node),
71
+ closure: nspin,
72
+ comments: attribute_comment(docs, true)
73
+ )
64
74
 
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))
75
+ pins.push Pin::InstanceVariable.new(name: "@#{attribute_name}",
76
+ closure: method_pin,
77
+ location: get_node_location(attribute_node),
78
+ comments: attribute_comment(docs, false))
79
+ end
80
+
81
+ pins.push method_pin
70
82
  end
71
83
  end
72
84
 
73
85
  process_children region.update(closure: nspin, visibility: :public)
86
+ false
74
87
  end
75
88
 
76
89
  private
77
90
 
78
91
  # @return [StructDefintionNode, nil]
79
92
  def struct_definition_node
80
- @struct_definition_node ||= if StructDefintionNode.valid?(node)
93
+ @struct_definition_node ||= if StructDefintionNode.match?(node)
81
94
  StructDefintionNode.new(node)
82
- elsif StructAssignmentNode.valid?(node)
95
+ elsif StructAssignmentNode.match?(node)
83
96
  StructAssignmentNode.new(node)
84
97
  end
85
98
  end
86
99
 
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?
100
+ # Gets/generates the relevant docstring for this struct & it's attributes
101
+ # @return [YARD::Docstring]
102
+ def docstring
103
+ @docstring ||= parse_comments
104
+ end
105
+
106
+ # Parses any relevant comments for a struct int a yard docstring
107
+ # @return [YARD::Docstring]
108
+ def parse_comments
109
+ struct_comments = comments_for(node) || ''
110
+ struct_definition_node.attributes.each do |attr_node, attr_name|
111
+ comment = comments_for(attr_node)
112
+ next if comment.nil?
113
+
114
+ # We should support specific comments for an attribute, and that can be either a @return on an @param
115
+ # But since we merge into the struct_comments, then we should interpret either as a param
116
+ comment = '@param ' + attr_name + comment[7..] if comment.start_with?('@return')
117
+
118
+ struct_comments += "\n#{comment}"
119
+ end
120
+
121
+ Solargraph::Source.parse_docstring(struct_comments).to_docstring
122
+ end
123
+
124
+ # @param tag [YARD::Tags::Tag, nil] The param tag for this attribute. If nil, this method is a no-op
125
+ # @param for_setter [Boolean] If true, will return a @param tag instead of a @return tag
126
+ def attribute_comment(tag, for_setter)
127
+ return "" if tag.nil?
128
+
129
+ suffix = "[#{tag.types&.join(',') || 'undefined'}] #{tag.text}"
92
130
 
93
- struct_comments.split("\n").find do |row|
94
- row.include?(attribute_name)
95
- end&.gsub('@param', '@return')&.gsub(attribute_name, '')
131
+ if for_setter
132
+ "@param #{tag.name} #{suffix}"
133
+ else
134
+ "@return #{suffix}"
135
+ end
96
136
  end
97
137
  end
98
138
  end
@@ -11,6 +11,7 @@ module Solargraph
11
11
  autoload :Gemspec, 'solargraph/convention/gemspec'
12
12
  autoload :Rakefile, 'solargraph/convention/rakefile'
13
13
  autoload :StructDefinition, 'solargraph/convention/struct_definition'
14
+ autoload :DataDefinition, 'solargraph/convention/data_definition'
14
15
 
15
16
  # @type [Set<Convention::Base>]
16
17
  @@conventions = Set.new
@@ -21,8 +21,9 @@ module Solargraph
21
21
 
22
22
  # @return [Array<Gem::Specification>]
23
23
  def uncached_gemspecs
24
- (uncached_yard_gemspecs + uncached_rbs_collection_gemspecs).sort.
25
- uniq { |gemspec| "#{gemspec.name}:#{gemspec.version}" }
24
+ uncached_yard_gemspecs.concat(uncached_rbs_collection_gemspecs)
25
+ .sort
26
+ .uniq { |gemspec| "#{gemspec.name}:#{gemspec.version}" }
26
27
  end
27
28
 
28
29
  # @return [Array<Gem::Specification>]
@@ -355,7 +356,10 @@ module Solargraph
355
356
  end
356
357
 
357
358
  def gemspecs_required_from_bundler
358
- if workspace&.directory && Bundler.definition&.lockfile&.to_s&.start_with?(workspace.directory)
359
+ # @todo Handle projects with custom Bundler/Gemfile setups
360
+ return unless workspace.gemfile?
361
+
362
+ if workspace.gemfile? && Bundler.definition&.lockfile&.to_s&.start_with?(workspace.directory)
359
363
  # Find only the gems bundler is now using
360
364
  Bundler.definition.locked_gems.specs.flat_map do |lazy_spec|
361
365
  logger.info "Handling #{lazy_spec.name}:#{lazy_spec.version}"
@@ -396,8 +400,7 @@ module Solargraph
396
400
  next specs
397
401
  end.compact
398
402
  else
399
- Solargraph.logger.warn e
400
- raise BundleNotFoundError, "Failed to load gems from bundle at #{workspace&.directory}"
403
+ Solargraph.logger.warn "Failed to load gems from bundle at #{workspace&.directory}: #{e}"
401
404
  end
402
405
  end
403
406
  end
@@ -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.
@@ -588,29 +589,52 @@ module Solargraph
588
589
  # @return [void]
589
590
  def cache_next_gemspec
590
591
  return if @cache_progress
591
- spec = (api_map.uncached_yard_gemspecs + api_map.uncached_rbs_collection_gemspecs).
592
- find { |spec| !cache_errors.include?(spec) }
592
+
593
+ spec = cacheable_specs.first
593
594
  return end_cache_progress unless spec
594
595
 
595
596
  pending = api_map.uncached_gemspecs.length - cache_errors.length - 1
596
- logger.info "Caching #{spec.name} #{spec.version}"
597
- Thread.new do
598
- cache_pid = Process.spawn(workspace.command_path, 'cache', spec.name, spec.version.to_s)
599
- report_cache_progress spec.name, pending
600
- Process.wait(cache_pid)
601
- logger.info "Cached #{spec.name} #{spec.version}"
602
- rescue Errno::EINVAL => _e
603
- logger.info "Cached #{spec.name} #{spec.version} with EINVAL"
604
- rescue StandardError => e
605
- cache_errors.add spec
606
- Solargraph.logger.warn "Error caching gemspec #{spec.name} #{spec.version}: [#{e.class}] #{e.message}"
607
- ensure
608
- 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
+
609
603
  catalog
610
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
611
621
  end
612
622
  end
613
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
+
614
638
  # @param gem_name [String]
615
639
  # @param pending [Integer]
616
640
  # @return [void]
@@ -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
 
@@ -36,10 +42,16 @@ module Solargraph
36
42
  )
37
43
  end
38
44
  return [pins, locals] unless Parser.is_ast_node?(node)
39
- klass = @@processors[node.type] || NodeProcessor::Base
40
- processor = klass.new(node, region, pins, locals)
41
- processor.process
42
- [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]
43
55
  end
44
56
  end
45
57
  end