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.
- checksums.yaml +4 -4
- data/.github/workflows/linting.yml +24 -2
- data/.github/workflows/plugins.yml +18 -0
- data/.github/workflows/rspec.yml +14 -1
- data/.github/workflows/typecheck.yml +6 -0
- data/CHANGELOG.md +38 -0
- data/lib/solargraph/api_map/index.rb +14 -3
- data/lib/solargraph/api_map/store.rb +6 -0
- data/lib/solargraph/api_map.rb +34 -2
- data/lib/solargraph/collection/gem.rb +12 -10
- data/lib/solargraph/collection.rb +0 -1
- data/lib/solargraph/complex_type/unique_type.rb +13 -0
- data/lib/solargraph/complex_type.rb +23 -3
- data/lib/solargraph/convention.rb +0 -4
- data/lib/solargraph/diagnostics/type_check.rb +0 -1
- data/lib/solargraph/external.rb +56 -28
- data/lib/solargraph/language_server/host/diagnoser.rb +1 -1
- data/lib/solargraph/language_server/host/dispatch.rb +4 -2
- data/lib/solargraph/language_server/host.rb +10 -2
- data/lib/solargraph/language_server/message/completion_item/resolve.rb +10 -3
- data/lib/solargraph/language_server/message/text_document/definition.rb +1 -1
- data/lib/solargraph/library.rb +19 -33
- data/lib/solargraph/parser/node_processor/base.rb +6 -0
- data/lib/solargraph/parser/parser_gem/node_methods.rb +29 -6
- data/lib/solargraph/parser/parser_gem/node_processors/class_definition/class_assignment_node.rb +98 -0
- data/lib/solargraph/parser/parser_gem/node_processors/class_definition.rb +54 -0
- data/lib/solargraph/parser/parser_gem/node_processors/cvasgn_node.rb +1 -0
- data/lib/solargraph/parser/parser_gem/node_processors/data_definition/data_assignment_node.rb +65 -0
- data/lib/solargraph/parser/parser_gem/node_processors/data_definition/data_definition_node.rb +97 -0
- data/lib/solargraph/parser/parser_gem/node_processors/data_definition.rb +110 -0
- data/lib/solargraph/parser/parser_gem/node_processors/gvasgn_node.rb +1 -0
- data/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +2 -0
- data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +1 -0
- data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +185 -0
- data/lib/solargraph/parser/parser_gem/node_processors/struct_definition/struct_assignment_node.rb +66 -0
- data/lib/solargraph/parser/parser_gem/node_processors/struct_definition/struct_definition_node.rb +108 -0
- data/lib/solargraph/parser/parser_gem/node_processors/struct_definition.rb +170 -0
- data/lib/solargraph/parser/parser_gem/node_processors.rb +33 -29
- data/lib/solargraph/pin/base.rb +25 -1
- data/lib/solargraph/pin/base_variable.rb +31 -1
- data/lib/solargraph/pin/callable.rb +11 -2
- data/lib/solargraph/pin/delegated_method.rb +5 -0
- data/lib/solargraph/pin/duck_method.rb +36 -16
- data/lib/solargraph/pin/instance_variable.rb +45 -0
- data/lib/solargraph/pin/method.rb +10 -0
- data/lib/solargraph/pin/method_alias.rb +4 -0
- data/lib/solargraph/pin/parameter.rb +1 -1
- data/lib/solargraph/pin/reference/included_mixin.rb +22 -0
- data/lib/solargraph/pin/reference.rb +1 -0
- data/lib/solargraph/plugin/active_support.rb +100 -0
- data/lib/solargraph/plugin/addon.rb +16 -0
- data/lib/solargraph/plugin/base.rb +25 -0
- data/lib/solargraph/plugin/panel.rb +37 -0
- data/lib/solargraph/plugin.rb +26 -0
- data/lib/solargraph/rbs_collection.rb +119 -0
- data/lib/solargraph/rbs_map/conversions.rb +106 -47
- data/lib/solargraph/rbs_map/gem.rb +4 -1
- data/lib/solargraph/rbs_map/helpers.rb +7 -6
- data/lib/solargraph/rbs_translator.rb +105 -70
- data/lib/solargraph/repo.rb +1 -1
- data/lib/solargraph/source/chain/call.rb +32 -13
- data/lib/solargraph/source/node_map.rb +27 -0
- data/lib/solargraph/source.rb +17 -0
- data/lib/solargraph/source_map/call.rb +18 -0
- data/lib/solargraph/source_map/mapper.rb +1 -1
- data/lib/solargraph/source_map.rb +18 -4
- data/lib/solargraph/type_checker/rules.rb +2 -2
- data/lib/solargraph/type_checker.rb +4 -1
- data/lib/solargraph/version.rb +1 -1
- data/lib/solargraph/yard_map/macro.rb +51 -1
- data/lib/solargraph.rb +2 -0
- data/rbs/fills/boolean/0/boolean.rbs +12 -0
- data/solargraph.gemspec +1 -1
- metadata +21 -11
- data/lib/solargraph/collection/rbs.rb +0 -49
- data/lib/solargraph/convention/active_support_concern.rb +0 -111
- data/lib/solargraph/convention/data_definition/data_assignment_node.rb +0 -61
- data/lib/solargraph/convention/data_definition/data_definition_node.rb +0 -93
- data/lib/solargraph/convention/data_definition.rb +0 -108
- data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +0 -62
- data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +0 -103
- data/lib/solargraph/convention/struct_definition.rb +0 -168
|
@@ -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
|
)
|
|
@@ -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
|
data/lib/solargraph/parser/parser_gem/node_processors/struct_definition/struct_assignment_node.rb
ADDED
|
@@ -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
|
data/lib/solargraph/parser/parser_gem/node_processors/struct_definition/struct_definition_node.rb
ADDED
|
@@ -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
|