solargraph 0.61.0 → 0.62.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/linting.yml +24 -2
  3. data/.github/workflows/plugins.yml +18 -0
  4. data/.github/workflows/rspec.yml +14 -1
  5. data/.github/workflows/typecheck.yml +6 -0
  6. data/CHANGELOG.md +38 -0
  7. data/lib/solargraph/api_map/index.rb +14 -3
  8. data/lib/solargraph/api_map/store.rb +6 -0
  9. data/lib/solargraph/api_map.rb +34 -2
  10. data/lib/solargraph/collection/gem.rb +12 -10
  11. data/lib/solargraph/collection.rb +0 -1
  12. data/lib/solargraph/complex_type/unique_type.rb +13 -0
  13. data/lib/solargraph/complex_type.rb +23 -3
  14. data/lib/solargraph/convention.rb +0 -4
  15. data/lib/solargraph/diagnostics/type_check.rb +0 -1
  16. data/lib/solargraph/external.rb +56 -28
  17. data/lib/solargraph/language_server/host/diagnoser.rb +1 -1
  18. data/lib/solargraph/language_server/host/dispatch.rb +4 -2
  19. data/lib/solargraph/language_server/host.rb +10 -2
  20. data/lib/solargraph/language_server/message/completion_item/resolve.rb +10 -3
  21. data/lib/solargraph/language_server/message/text_document/definition.rb +1 -1
  22. data/lib/solargraph/library.rb +19 -33
  23. data/lib/solargraph/parser/node_processor/base.rb +6 -0
  24. data/lib/solargraph/parser/parser_gem/node_methods.rb +29 -6
  25. data/lib/solargraph/parser/parser_gem/node_processors/class_definition/class_assignment_node.rb +98 -0
  26. data/lib/solargraph/parser/parser_gem/node_processors/class_definition.rb +54 -0
  27. data/lib/solargraph/parser/parser_gem/node_processors/cvasgn_node.rb +1 -0
  28. data/lib/solargraph/parser/parser_gem/node_processors/data_definition/data_assignment_node.rb +65 -0
  29. data/lib/solargraph/parser/parser_gem/node_processors/data_definition/data_definition_node.rb +97 -0
  30. data/lib/solargraph/parser/parser_gem/node_processors/data_definition.rb +110 -0
  31. data/lib/solargraph/parser/parser_gem/node_processors/gvasgn_node.rb +1 -0
  32. data/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +2 -0
  33. data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +1 -0
  34. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +185 -0
  35. data/lib/solargraph/parser/parser_gem/node_processors/struct_definition/struct_assignment_node.rb +66 -0
  36. data/lib/solargraph/parser/parser_gem/node_processors/struct_definition/struct_definition_node.rb +108 -0
  37. data/lib/solargraph/parser/parser_gem/node_processors/struct_definition.rb +170 -0
  38. data/lib/solargraph/parser/parser_gem/node_processors.rb +33 -29
  39. data/lib/solargraph/pin/base.rb +25 -1
  40. data/lib/solargraph/pin/base_variable.rb +31 -1
  41. data/lib/solargraph/pin/callable.rb +11 -2
  42. data/lib/solargraph/pin/delegated_method.rb +5 -0
  43. data/lib/solargraph/pin/duck_method.rb +36 -16
  44. data/lib/solargraph/pin/instance_variable.rb +45 -0
  45. data/lib/solargraph/pin/method.rb +10 -0
  46. data/lib/solargraph/pin/method_alias.rb +4 -0
  47. data/lib/solargraph/pin/parameter.rb +1 -1
  48. data/lib/solargraph/pin/reference/included_mixin.rb +22 -0
  49. data/lib/solargraph/pin/reference.rb +1 -0
  50. data/lib/solargraph/plugin/active_support.rb +100 -0
  51. data/lib/solargraph/plugin/addon.rb +16 -0
  52. data/lib/solargraph/plugin/base.rb +25 -0
  53. data/lib/solargraph/plugin/panel.rb +37 -0
  54. data/lib/solargraph/plugin.rb +26 -0
  55. data/lib/solargraph/rbs_collection.rb +119 -0
  56. data/lib/solargraph/rbs_map/conversions.rb +106 -47
  57. data/lib/solargraph/rbs_map/gem.rb +4 -1
  58. data/lib/solargraph/rbs_map/helpers.rb +7 -6
  59. data/lib/solargraph/rbs_translator.rb +105 -70
  60. data/lib/solargraph/repo.rb +1 -1
  61. data/lib/solargraph/source/chain/call.rb +32 -13
  62. data/lib/solargraph/source/node_map.rb +27 -0
  63. data/lib/solargraph/source.rb +17 -0
  64. data/lib/solargraph/source_map/call.rb +18 -0
  65. data/lib/solargraph/source_map/mapper.rb +1 -1
  66. data/lib/solargraph/source_map.rb +18 -4
  67. data/lib/solargraph/type_checker/rules.rb +2 -2
  68. data/lib/solargraph/type_checker.rb +4 -1
  69. data/lib/solargraph/version.rb +1 -1
  70. data/lib/solargraph/yard_map/macro.rb +51 -1
  71. data/lib/solargraph.rb +2 -0
  72. data/rbs/fills/boolean/0/boolean.rbs +12 -0
  73. data/solargraph.gemspec +1 -1
  74. metadata +21 -11
  75. data/lib/solargraph/collection/rbs.rb +0 -49
  76. data/lib/solargraph/convention/active_support_concern.rb +0 -111
  77. data/lib/solargraph/convention/data_definition/data_assignment_node.rb +0 -61
  78. data/lib/solargraph/convention/data_definition/data_definition_node.rb +0 -93
  79. data/lib/solargraph/convention/data_definition.rb +0 -108
  80. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +0 -62
  81. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +0 -103
  82. data/lib/solargraph/convention/struct_definition.rb +0 -168
@@ -12,6 +12,7 @@ module Solargraph
12
12
  closure: region.closure,
13
13
  name: node.children[0].to_s,
14
14
  comments: comments_for(node),
15
+ type_assertion: type_assertion_for(node),
15
16
  assignment: node.children[1],
16
17
  source: :parser
17
18
  )
@@ -14,6 +14,7 @@ module Solargraph
14
14
  closure: region.closure,
15
15
  name: node.children[0].to_s,
16
16
  comments: comments_for(node),
17
+ type_assertion: type_assertion_for(node),
17
18
  assignment: node.children[1],
18
19
  source: :parser
19
20
  )
@@ -28,6 +29,7 @@ module Solargraph
28
29
  name: region.closure.name),
29
30
  name: node.children[0].to_s,
30
31
  comments: comments_for(node),
32
+ type_assertion: type_assertion_for(node),
31
33
  assignment: node.children[1],
32
34
  source: :parser
33
35
  )
@@ -18,6 +18,7 @@ module Solargraph
18
18
  name: node.children[0].to_s,
19
19
  assignment: node.children[1],
20
20
  comments: comments_for(node),
21
+ type_assertion: type_assertion_for(node),
21
22
  presence: presence,
22
23
  source: :parser
23
24
  )
@@ -40,10 +40,16 @@ module Solargraph
40
40
  process_private_constant
41
41
  elsif method_name == :alias_method && node.children[2] && node.children[2] && node.children[2].type == :sym && node.children[3] && node.children[3].type == :sym
42
42
  process_alias_method
43
+ elsif %i[def_delegator def_delegators].include?(method_name) && extends_forwardable?
44
+ process_def_delegators
43
45
  elsif method_name == :private_class_method && node.children[2].is_a?(AST::Node)
44
46
  # Processing a private class can potentially handle children on its own
45
47
  return if process_private_class_method
46
48
  end
49
+ elsif %i[include extend prepend].include?(method_name) && Parser.is_ast_node?(node.children[0]) && node.children[0].type == :const
50
+ process_qualified_mixin method_name
51
+ elsif included_hook_parameter?(node.children[0])
52
+ process_included_hook_mixin method_name
47
53
  elsif method_name == :require && node.children[0].to_s == '(const nil :Bundler)'
48
54
  pins.push Pin::Reference::Require.new(
49
55
  Solargraph::Location.new(region.filename,
@@ -55,6 +61,91 @@ module Solargraph
55
61
 
56
62
  private
57
63
 
64
+ # True when the enclosing namespace extends Forwardable, which is
65
+ # what makes def_delegator and def_delegators available there.
66
+ # Matched by name only, so a same-named local module would fool this.
67
+ #
68
+ # @return [Boolean]
69
+ def extends_forwardable?
70
+ pins.any? do |pin|
71
+ pin.is_a?(Pin::Reference::Extend) &&
72
+ pin.closure == region.closure &&
73
+ ['Forwardable', '::Forwardable'].include?(pin.name)
74
+ end
75
+ end
76
+
77
+ # The word behind a symbol or string node, e.g. :@records or "size".
78
+ #
79
+ # @param subject [Object]
80
+ # @return [String, nil]
81
+ def symbol_word subject
82
+ return nil unless subject.is_a?(::Parser::AST::Node)
83
+
84
+ # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check
85
+ return nil unless %i[sym str].include?(subject.type)
86
+
87
+ # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check
88
+ subject.children.first.to_s
89
+ end
90
+
91
+ # Forwardable's def_delegator/def_delegators define real methods
92
+ # whose behavior - including return type - comes from the method
93
+ # they forward to. Map them to DelegatedMethod pins, which resolve
94
+ # the receiver and the forwarded method lazily.
95
+ #
96
+ # @return [void]
97
+ def process_def_delegators
98
+ receiver_word = symbol_word(node.children[2])
99
+ return if receiver_word.nil?
100
+
101
+ receiver_chain = delegation_receiver_chain(receiver_word)
102
+ if node.children[1] == :def_delegator
103
+ receiver_method_name = symbol_word(node.children[3])
104
+ return if receiver_method_name.nil?
105
+
106
+ name = symbol_word(node.children[4]) || receiver_method_name
107
+ push_delegated_method(name, receiver_method_name, receiver_chain)
108
+ else
109
+ (node.children[3..] || []).each do |method_node|
110
+ word = symbol_word(method_node)
111
+ next if word.nil?
112
+
113
+ push_delegated_method(word, word, receiver_chain)
114
+ end
115
+ end
116
+ end
117
+
118
+ # @param word [String] the delegation target, e.g. "@records" or "records"
119
+ # @return [Source::Chain]
120
+ def delegation_receiver_chain word
121
+ link = if word.start_with?('@@')
122
+ Source::Chain::ClassVariable.new(word)
123
+ elsif word.start_with?('@')
124
+ Source::Chain::InstanceVariable.new(word, node, get_node_location(node))
125
+ else
126
+ Source::Chain::Call.new(word, get_node_location(node))
127
+ end
128
+ Source::Chain.new([link])
129
+ end
130
+
131
+ # @param name [String]
132
+ # @param receiver_method_name [String]
133
+ # @param receiver_chain [Source::Chain]
134
+ # @return [void]
135
+ def push_delegated_method name, receiver_method_name, receiver_chain
136
+ pins.push Solargraph::Pin::DelegatedMethod.new(
137
+ location: get_node_location(node),
138
+ closure: region.closure,
139
+ name: name,
140
+ receiver: receiver_chain,
141
+ receiver_method_name: receiver_method_name,
142
+ scope: region.scope || :instance,
143
+ visibility: region.visibility,
144
+ comments: comments_for(node),
145
+ source: :parser
146
+ )
147
+ end
148
+
58
149
  # @return [void]
59
150
  def process_visibility
60
151
  if node.children.length > 2
@@ -127,6 +218,20 @@ module Solargraph
127
218
  end
128
219
  end
129
220
 
221
+ # @param options [Hash{String => AST::Node}]
222
+ # @param name [String]
223
+ # @param default [Boolean]
224
+ # @return [Boolean]
225
+ def boolean_option options, name, default
226
+ value = options[name]
227
+ return default unless Parser.is_ast_node?(value)
228
+ # rubocop:disable Lint/BooleanSymbol -- these are AST node types
229
+ return true if value.type == :true
230
+ return false if value.type == :false
231
+ # rubocop:enable Lint/BooleanSymbol
232
+ default
233
+ end
234
+
130
235
  # @return [void]
131
236
  def process_include
132
237
  return unless node.children[2].is_a?(AST::Node) && node.children[2].type == :const
@@ -158,6 +263,42 @@ module Solargraph
158
263
  end
159
264
  end
160
265
 
266
+ # Process mixins called on an explicit receiver, e.g.,
267
+ # +Object.prepend(self)+ in ActiveSupport or
268
+ # +Integer.prepend ActiveSupport::NumericWithFormat+. Without this,
269
+ # gems that extend core classes this way lose every method they
270
+ # share with Object, Module, Integer, etc.
271
+ #
272
+ # @param keyword [::Symbol] one of :include, :extend, :prepend
273
+ # @return [void]
274
+ def process_qualified_mixin keyword
275
+ reference_class = {
276
+ include: Pin::Reference::Include,
277
+ extend: Pin::Reference::Extend,
278
+ prepend: Pin::Reference::Prepend
279
+ }[keyword]
280
+ return if reference_class.nil?
281
+ receiver = node.children[0]
282
+ target = unpack_name(receiver)
283
+ return if target.nil? || target.empty?
284
+ closure = Pin::Namespace.new(location: get_node_location(receiver), name: target, source: :parser)
285
+ node.children[2..].each do |arg|
286
+ next unless Parser.is_ast_node?(arg)
287
+ name = if arg.type == :self
288
+ region.closure&.full_context&.namespace
289
+ elsif arg.type == :const
290
+ unpack_name(arg)
291
+ end
292
+ next if name.nil? || name.empty?
293
+ pins.push reference_class.new(
294
+ location: get_node_location(arg),
295
+ closure: closure,
296
+ name: name,
297
+ source: :parser
298
+ )
299
+ end
300
+ end
301
+
161
302
  # @return [void]
162
303
  def process_extend
163
304
  # @sg-ignore Need to add nil check here
@@ -181,6 +322,50 @@ module Solargraph
181
322
  end
182
323
  end
183
324
 
325
+ # @param receiver [::Parser::AST::Node, nil]
326
+ # @return [Boolean] whether receiver is the parameter of a module's
327
+ # +self.included(base)+ hook
328
+ def included_hook_parameter? receiver
329
+ hook = region.closure
330
+ return false if receiver.nil? || receiver.type != :lvar
331
+ return false unless hook.is_a?(Pin::Method) && hook.scope == :class && hook.name == 'included'
332
+ hook.parameters.first&.name == receiver.children[0].to_s
333
+ end
334
+
335
+ # Map +base.extend ClassMethods+, +base.send(:include, Helpers)+
336
+ # and similar calls in a +self.included(base)+ hook, which apply to
337
+ # every class that includes the hook's module
338
+ #
339
+ # @param method_name [::Symbol]
340
+ # @return [void]
341
+ def process_included_hook_mixin method_name
342
+ args = node.children[2..] || []
343
+ if %i[send public_send].include?(method_name)
344
+ keyword = args.first
345
+ return unless Parser.is_ast_node?(keyword) && keyword.type == :sym
346
+ method_name = keyword.children[0]
347
+ args = args.drop(1)
348
+ end
349
+ return unless %i[include extend prepend].include?(method_name)
350
+ hook = region.closure
351
+ args.each do |arg|
352
+ next unless Parser.is_ast_node?(arg)
353
+ name = if arg.type == :self
354
+ hook.namespace
355
+ elsif arg.type == :const
356
+ unpack_name(arg)
357
+ end
358
+ next if name.nil? || name.empty?
359
+ pins.push Pin::Reference::IncludedMixin.new(
360
+ location: get_node_location(arg),
361
+ closure: hook.closure,
362
+ name: name,
363
+ keyword: method_name,
364
+ source: :parser
365
+ )
366
+ end
367
+ end
368
+
184
369
  # @return [void]
185
370
  def process_require
186
371
  return unless node.children[2].is_a?(AST::Node) && node.children[2].type == :str
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Parser
5
+ module ParserGem
6
+ module NodeProcessors
7
+ module StructDefinition
8
+ # A node wrapper for a Struct definition via const assignment.
9
+ # @example
10
+ # MyStruct = Struct.new(:bar, :baz) do
11
+ # def foo
12
+ # end
13
+ # end
14
+ class StructAssignmentNode < StructDefintionNode
15
+ class << self
16
+ # @example
17
+ # s(:casgn, nil, :Foo,
18
+ # s(:block,
19
+ # s(:send,
20
+ # s(:const, nil, :Struct), :new,
21
+ # s(:sym, :bar),
22
+ # s(:sym, :baz)),
23
+ # s(:args),
24
+ # s(:def, :foo,
25
+ # s(:args),
26
+ # s(:send, nil, :bar))))
27
+ #
28
+ # @param node [Parser::AST::Node]
29
+ def match? node
30
+ return false unless node&.type == :casgn
31
+ return false if node.children[2].nil?
32
+
33
+ struct_node = if node.children[2].type == :block
34
+ node.children[2].children[0]
35
+ else
36
+ node.children[2]
37
+ end
38
+
39
+ struct_definition_node?(struct_node)
40
+ end
41
+ end
42
+
43
+ def class_name
44
+ if node.children[0]
45
+ Parser::NodeMethods.unpack_name(node.children[0]) + "::#{node.children[1]}"
46
+ else
47
+ node.children[1].to_s
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ # @return [Parser::AST::Node]
54
+ def struct_node
55
+ if node.children[2].type == :block
56
+ node.children[2].children[0]
57
+ else
58
+ node.children[2]
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Parser
5
+ module ParserGem
6
+ module NodeProcessors
7
+ module StructDefinition
8
+ # A node wrapper for a Struct definition via inheritance.
9
+ # @example
10
+ # class MyStruct < Struct.new(:bar, :baz)
11
+ # def foo
12
+ # end
13
+ # end
14
+ class StructDefintionNode
15
+ class << self
16
+ # @example
17
+ # s(:class,
18
+ # s(:const, nil, :Foo),
19
+ # s(:send,
20
+ # s(:const, nil, :Struct), :new,
21
+ # s(:sym, :bar),
22
+ # s(:sym, :baz)),
23
+ # s(:hash,
24
+ # s(:pair,
25
+ # s(:sym, :keyword_init),
26
+ # s(:true)))),
27
+ # s(:def, :foo,
28
+ # s(:args),
29
+ # s(:send, nil, :bar)))
30
+ #
31
+ # @param node [Parser::AST::Node]
32
+ def match? node
33
+ return false unless node&.type == :class
34
+
35
+ struct_definition_node?(node.children[1])
36
+ end
37
+
38
+ private
39
+
40
+ # @param struct_node [Parser::AST::Node]
41
+ # @return [Boolean]
42
+ def struct_definition_node? struct_node
43
+ return false unless struct_node.is_a?(::Parser::AST::Node)
44
+ return false unless struct_node&.type == :send
45
+ return false unless struct_node.children[0]&.type == :const
46
+ return false unless struct_node.children[0].children[1] == :Struct
47
+ return false unless struct_node.children[1] == :new
48
+
49
+ true
50
+ end
51
+ end
52
+
53
+ # @param node [Parser::AST::Node]
54
+ def initialize node
55
+ @node = node
56
+ end
57
+
58
+ # @return [String]
59
+ def class_name
60
+ Parser::NodeMethods.unpack_name(node)
61
+ end
62
+
63
+ # @return [Array<Array(Parser::AST::Node, String)>]
64
+ def attributes
65
+ struct_attribute_nodes.map do |struct_def_param|
66
+ next unless struct_def_param.type == :sym
67
+ [struct_def_param, struct_def_param.children[0].to_s]
68
+ end.compact
69
+ end
70
+
71
+ def keyword_init?
72
+ keyword_init_param = struct_attribute_nodes.find do |struct_def_param|
73
+ struct_def_param.type == :hash && struct_def_param.children[0].type == :pair &&
74
+ struct_def_param.children[0].children[0].children[0] == :keyword_init
75
+ end
76
+
77
+ return false if keyword_init_param.nil?
78
+
79
+ # rubocop:disable-next Lint/BooleanSymbol
80
+ keyword_init_param.children[0].children[1].type == :true
81
+ end
82
+
83
+ # @return [Parser::AST::Node]
84
+ def body_node
85
+ node.children[2]
86
+ end
87
+
88
+ private
89
+
90
+ # @return [Parser::AST::Node]
91
+ attr_reader :node
92
+
93
+ # @return [Parser::AST::Node]
94
+ def struct_node
95
+ node.children[1]
96
+ end
97
+
98
+ # @sg-ignore Need to add nil check here
99
+ # @return [Array<Parser::AST::Node>]
100
+ def struct_attribute_nodes
101
+ struct_node.children[2..]
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Parser
5
+ module ParserGem
6
+ module NodeProcessors
7
+ module StructDefinition
8
+ autoload :StructDefintionNode, 'solargraph/parser/parser_gem/node_processors/struct_definition/struct_definition_node'
9
+ autoload :StructAssignmentNode, 'solargraph/parser/parser_gem/node_processors/struct_definition/struct_assignment_node'
10
+
11
+ class StructNode < Parser::NodeProcessor::Base
12
+ # @return [Boolean] continue processing the next processor of the same node.
13
+ def process
14
+ return true if struct_definition_node.nil?
15
+
16
+ loc = get_node_location(node)
17
+ nspin = Solargraph::Pin::Namespace.new(
18
+ type: :class,
19
+ location: loc,
20
+ closure: region.closure,
21
+ # @sg-ignore flow sensitive typing needs to handle attrs
22
+ name: struct_definition_node.class_name,
23
+ docstring: docstring,
24
+ visibility: :public,
25
+ gates: region.closure.gates.freeze,
26
+ source: :struct_definition
27
+ )
28
+ pins.push nspin
29
+
30
+ # define initialize method
31
+ initialize_method_pin = Pin::Method.new(
32
+ name: 'initialize',
33
+ parameters: [],
34
+ scope: :instance,
35
+ location: get_node_location(node),
36
+ closure: nspin,
37
+ visibility: :private,
38
+ docstring: docstring,
39
+ source: :struct_definition
40
+ )
41
+
42
+ pins.push initialize_method_pin
43
+
44
+ # @sg-ignore flow sensitive typing needs to handle attrs
45
+ struct_definition_node.attributes.map do |attribute_node, attribute_name|
46
+ initialize_method_pin.parameters.push(
47
+ Pin::Parameter.new(
48
+ name: attribute_name,
49
+ decl: struct_definition_node.keyword_init? ? :kwoptarg : :arg,
50
+ location: get_node_location(attribute_node),
51
+ closure: initialize_method_pin,
52
+ source: :struct_definition
53
+ )
54
+ )
55
+ end
56
+
57
+ # define attribute accessors and instance variables
58
+ # @sg-ignore flow sensitive typing needs to handle attrs
59
+ struct_definition_node.attributes.each do |attribute_node, attribute_name|
60
+ [attribute_name, "#{attribute_name}="].each do |name|
61
+ docs = docstring.tags.find { |t| t.tag_name == 'param' && t.name == attribute_name }
62
+
63
+ attribute_type = ComplexType.parse(tag_string(docs))
64
+ return_type_comment = attribute_comment(docs, false)
65
+ param_comment = attribute_comment(docs, true)
66
+
67
+ method_pin = Pin::Method.new(
68
+ name: name,
69
+ parameters: [],
70
+ scope: :instance,
71
+ location: get_node_location(attribute_node),
72
+ closure: nspin,
73
+ docstring: YARD::Docstring.new(return_type_comment),
74
+ # even assignments return the value
75
+ comments: return_type_comment,
76
+ return_type: attribute_type,
77
+ visibility: :public,
78
+ source: :struct_definition
79
+ )
80
+
81
+ if name.end_with?('=')
82
+ method_pin.parameters << Pin::Parameter.new(
83
+ name: attribute_name,
84
+ location: get_node_location(attribute_node),
85
+ closure: method_pin,
86
+ return_type: attribute_type,
87
+ comments: param_comment,
88
+ source: :struct_definition
89
+ )
90
+
91
+ pins.push Pin::InstanceVariable.new(name: "@#{attribute_name}",
92
+ closure: method_pin,
93
+ location: get_node_location(attribute_node),
94
+ return_type: attribute_type,
95
+ comments: "@type [#{attribute_type.rooted_tags}]",
96
+ source: :struct_definition)
97
+ end
98
+
99
+ pins.push method_pin
100
+ end
101
+ end
102
+
103
+ process_children region.update(closure: nspin, visibility: :public)
104
+ false
105
+ end
106
+
107
+ private
108
+
109
+ # @return [StructDefinition::StructDefintionNode, StructDefinition::StructAssignmentNode, nil]
110
+ def struct_definition_node
111
+ @struct_definition_node ||= if StructDefintionNode.match?(node)
112
+ StructDefintionNode.new(node)
113
+ elsif StructAssignmentNode.match?(node)
114
+ StructAssignmentNode.new(node)
115
+ end
116
+ end
117
+
118
+ # Gets/generates the relevant docstring for this struct & it's attributes
119
+ # @return [YARD::Docstring]
120
+ def docstring
121
+ @docstring ||= parse_comments
122
+ end
123
+
124
+ # Parses any relevant comments for a struct int a yard docstring
125
+ # @return [YARD::Docstring]
126
+ def parse_comments
127
+ struct_comments = comments_for(node) || ''
128
+ # @sg-ignore Need to add nil check here
129
+ struct_definition_node.attributes.each do |attr_node, attr_name|
130
+ comment = comments_for(attr_node)
131
+ next if comment.nil?
132
+
133
+ # We should support specific comments for an attribute, and that can be either a @return on an @param
134
+ # But since we merge into the struct_comments, then we should interpret either as a param
135
+ comment = "@param #{attr_name}#{comment[7..]}" if comment.start_with?('@return')
136
+
137
+ struct_comments += "\n#{comment}"
138
+ end
139
+
140
+ Solargraph::Source.parse_docstring(struct_comments).to_docstring
141
+ end
142
+
143
+ # @param tag [YARD::Tags::Tag, nil] The param tag for this attribute.xtract_
144
+ #
145
+ # @return [String]
146
+ def tag_string tag
147
+ tag&.types&.join(',') || 'undefined'
148
+ end
149
+
150
+ # @param tag [YARD::Tags::Tag, nil] The param tag for this attribute. If nil, this method is a no-op
151
+ # @param for_setter [Boolean] If true, will return a @param tag instead of a @return tag
152
+ #
153
+ # @return [String] The formatted comment for the attribute
154
+ def attribute_comment tag, for_setter
155
+ return '' if tag.nil?
156
+
157
+ suffix = "[#{tag_string(tag)}] #{tag.text}"
158
+
159
+ if for_setter
160
+ "@param #{tag.name} #{suffix}"
161
+ else
162
+ "@return #{suffix}"
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end