solargraph 0.54.5 → 0.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/lib/solargraph/api_map/store.rb +3 -1
- data/lib/solargraph/api_map.rb +18 -8
- data/lib/solargraph/complex_type/unique_type.rb +88 -7
- data/lib/solargraph/complex_type.rb +35 -6
- data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +51 -0
- data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +100 -0
- data/lib/solargraph/convention/struct_definition.rb +101 -0
- data/lib/solargraph/convention.rb +1 -0
- data/lib/solargraph/doc_map.rb +42 -18
- data/lib/solargraph/language_server/host/message_worker.rb +10 -7
- data/lib/solargraph/language_server/host.rb +1 -0
- data/lib/solargraph/location.rb +8 -0
- data/lib/solargraph/parser/comment_ripper.rb +11 -6
- data/lib/solargraph/parser/flow_sensitive_typing.rb +226 -0
- data/lib/solargraph/parser/node_methods.rb +14 -0
- data/lib/solargraph/parser/node_processor.rb +0 -1
- data/lib/solargraph/parser/parser_gem/class_methods.rb +9 -0
- data/lib/solargraph/parser/parser_gem/node_chainer.rb +10 -10
- data/lib/solargraph/parser/parser_gem/node_methods.rb +3 -1
- data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +21 -0
- data/lib/solargraph/parser/parser_gem/node_processors/casgn_node.rb +21 -1
- data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +21 -0
- data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +26 -5
- data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +41 -0
- data/lib/solargraph/parser/parser_gem/node_processors/until_node.rb +28 -0
- data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +28 -0
- data/lib/solargraph/parser/parser_gem/node_processors.rb +10 -0
- data/lib/solargraph/parser.rb +1 -0
- data/lib/solargraph/pin/base.rb +9 -3
- data/lib/solargraph/pin/base_variable.rb +7 -1
- data/lib/solargraph/pin/block.rb +2 -0
- data/lib/solargraph/pin/breakable.rb +9 -0
- data/lib/solargraph/pin/local_variable.rb +7 -1
- data/lib/solargraph/pin/method.rb +20 -18
- data/lib/solargraph/pin/namespace.rb +10 -7
- data/lib/solargraph/pin/parameter.rb +13 -5
- data/lib/solargraph/pin/proxy_type.rb +12 -6
- data/lib/solargraph/pin/until.rb +18 -0
- data/lib/solargraph/pin/while.rb +18 -0
- data/lib/solargraph/pin.rb +3 -0
- data/lib/solargraph/rbs_map/conversions.rb +8 -8
- data/lib/solargraph/rbs_map/core_fills.rb +10 -3
- data/lib/solargraph/source/chain/array.rb +4 -3
- data/lib/solargraph/source/chain/call.rb +46 -17
- data/lib/solargraph/source/chain/constant.rb +1 -1
- data/lib/solargraph/source/chain/hash.rb +3 -2
- data/lib/solargraph/source/chain/link.rb +2 -0
- data/lib/solargraph/source/chain/literal.rb +22 -2
- data/lib/solargraph/source/chain/z_super.rb +1 -1
- data/lib/solargraph/source/chain.rb +77 -47
- data/lib/solargraph/source/source_chainer.rb +2 -2
- data/lib/solargraph/source_map/clip.rb +3 -1
- data/lib/solargraph/type_checker/checks.rb +4 -0
- data/lib/solargraph/type_checker.rb +35 -8
- data/lib/solargraph/version.rb +1 -1
- data/lib/solargraph/yard_map/mapper/to_method.rb +42 -15
- metadata +14 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c8e98755e559a9524b7d057c860e1ebb6d8a46eeb73dd567744f7562a4d49a64
|
4
|
+
data.tar.gz: 33943305fcf0c4c534fd4abf5eb24a937f6390021e681017296aac7bb9546592
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 37c0b331d67d299a865e4201c030dec6c1fd4ca14955012d702a44849bece0497c70449a3594ce8cdfe56669b93fbc80d1b291331a540453409a990344464ac1
|
7
|
+
data.tar.gz: c91397eeb5d2f10c551e97a741acb9a1e8fbae371b810bf375cc6024fc5985d6e2e581fb1d39c62ba9dd7f71b2cc6fa0ca801fcb74c89b5b8717beeaf8af10f0
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
## 0.55.0 - June 3, 2025
|
2
|
+
- Flow-sensitive typing - automatically downcast from is_a? calls (#856)
|
3
|
+
- Tuple enabler: infer literal types and use them for signature selection (#836)
|
4
|
+
- Signature selection improvements (#907)
|
5
|
+
- Add support for Ruby Structs (#939)
|
6
|
+
- [regression] Fix interface change breaking solargraph-rails (#940)
|
7
|
+
- [regression] Add back bundler/require support for solargraph-rails (#941)
|
8
|
+
- Add specs for initialize capabilities (#955)
|
9
|
+
- Create MethodAlias pins from YARD (#945)
|
10
|
+
- MessageWorker prioritizes synchronization (#956)
|
11
|
+
- initialize/new method pin cleanups (#949)
|
12
|
+
- Clip rebinds blocks when cursor is not part of receiver (#958)
|
13
|
+
|
1
14
|
## 0.54.5 - May 17, 2025
|
2
15
|
- Repair unknown encoding errors (#936, #935)
|
3
16
|
- Index arbitrary pinsets (#937)
|
@@ -73,6 +73,8 @@ module Solargraph
|
|
73
73
|
return superclass_references[fqns].first if superclass_references.key?(fqns)
|
74
74
|
return 'Object' if fqns != 'BasicObject' && namespace_exists?(fqns)
|
75
75
|
return 'Object' if fqns == 'Boolean'
|
76
|
+
simplified_literal_name = ComplexType.parse("#{fqns}").simplify_literals.name
|
77
|
+
return simplified_literal_name if simplified_literal_name != fqns
|
76
78
|
nil
|
77
79
|
end
|
78
80
|
|
@@ -112,7 +114,7 @@ module Solargraph
|
|
112
114
|
# @param fqns [String]
|
113
115
|
# @return [Enumerable<Solargraph::Pin::Base>]
|
114
116
|
def get_class_variables(fqns)
|
115
|
-
namespace_children(fqns).select{|pin| pin.is_a?(Pin::ClassVariable)}
|
117
|
+
namespace_children(fqns).select { |pin| pin.is_a?(Pin::ClassVariable)}
|
116
118
|
end
|
117
119
|
|
118
120
|
# @return [Enumerable<Solargraph::Pin::Base>]
|
data/lib/solargraph/api_map.rb
CHANGED
@@ -264,15 +264,19 @@ module Solargraph
|
|
264
264
|
# Should not be prefixed with '::'.
|
265
265
|
# @return [String, nil] fully qualified tag
|
266
266
|
def qualify tag, context_tag = ''
|
267
|
-
return tag if ['self', nil].include?(tag)
|
267
|
+
return tag if ['Boolean', 'self', nil].include?(tag)
|
268
268
|
|
269
|
-
context_type = ComplexType.
|
269
|
+
context_type = ComplexType.try_parse(context_tag).force_rooted
|
270
270
|
return unless context_type
|
271
271
|
|
272
272
|
type = ComplexType.try_parse(tag)
|
273
273
|
return unless type
|
274
|
+
return tag if type.literal?
|
274
275
|
|
275
|
-
|
276
|
+
context_type = ComplexType.try_parse(context_tag)
|
277
|
+
return unless context_type
|
278
|
+
|
279
|
+
fqns = qualify_namespace(type.rooted_namespace, context_type.rooted_namespace)
|
276
280
|
return unless fqns
|
277
281
|
|
278
282
|
fqns + type.substring
|
@@ -319,6 +323,11 @@ module Solargraph
|
|
319
323
|
result
|
320
324
|
end
|
321
325
|
|
326
|
+
# @see Solargraph::Parser::FlowSensitiveTyping#visible_pins
|
327
|
+
def visible_pins(*args, **kwargs, &blk)
|
328
|
+
Solargraph::Parser::FlowSensitiveTyping.visible_pins(*args, **kwargs, &blk)
|
329
|
+
end
|
330
|
+
|
322
331
|
# Get an array of class variable pins for a namespace.
|
323
332
|
#
|
324
333
|
# @param namespace [String] A fully qualified namespace
|
@@ -376,7 +385,7 @@ module Solargraph
|
|
376
385
|
init_pin = get_method_stack(rooted_tag, 'initialize').first
|
377
386
|
next pin unless init_pin
|
378
387
|
|
379
|
-
type = ComplexType
|
388
|
+
type = ComplexType::SELF
|
380
389
|
Pin::Method.new(
|
381
390
|
name: 'new',
|
382
391
|
scope: :class,
|
@@ -385,9 +394,10 @@ module Solargraph
|
|
385
394
|
signatures: init_pin.signatures.map { |sig| sig.proxy(type) },
|
386
395
|
return_type: type,
|
387
396
|
comments: init_pin.comments,
|
388
|
-
closure: init_pin.closure
|
389
|
-
|
390
|
-
|
397
|
+
closure: init_pin.closure,
|
398
|
+
source: init_pin.source,
|
399
|
+
type_location: init_pin.type_location,
|
400
|
+
)
|
391
401
|
end
|
392
402
|
end
|
393
403
|
result.concat inner_get_methods('Kernel', :instance, [:public], deep, skip) if visibility.include?(:private)
|
@@ -814,7 +824,7 @@ module Solargraph
|
|
814
824
|
return pin unless pin.is_a?(Pin::MethodAlias)
|
815
825
|
return nil if @method_alias_stack.include?(pin.path)
|
816
826
|
@method_alias_stack.push pin.path
|
817
|
-
origin = get_method_stack(pin.full_context.tag, pin.original, scope: pin.scope).first
|
827
|
+
origin = get_method_stack(pin.full_context.tag, pin.original, scope: pin.scope, preserve_generics: true).first
|
818
828
|
@method_alias_stack.pop
|
819
829
|
return nil if origin.nil?
|
820
830
|
args = {
|
@@ -27,7 +27,7 @@ module Solargraph
|
|
27
27
|
# @return [UniqueType]
|
28
28
|
def self.parse name, substring = '', make_rooted: nil
|
29
29
|
if name.start_with?(':::')
|
30
|
-
raise "Illegal prefix: #{name}"
|
30
|
+
raise ComplexTypeError, "Illegal prefix: #{name}"
|
31
31
|
end
|
32
32
|
if name.start_with?('::')
|
33
33
|
name = name[2..-1]
|
@@ -48,7 +48,7 @@ module Solargraph
|
|
48
48
|
subs = ComplexType.parse(substring[1..-2], partial: true)
|
49
49
|
parameters_type = PARAMETERS_TYPE_BY_STARTING_TAG.fetch(substring[0])
|
50
50
|
if parameters_type == :hash
|
51
|
-
raise ComplexTypeError, "Bad hash type" unless !subs.is_a?(ComplexType) and subs.length == 2 and !subs[0].is_a?(UniqueType) and !subs[1].is_a?(UniqueType)
|
51
|
+
raise ComplexTypeError, "Bad hash type: name=#{name}, substring=#{substring}" unless !subs.is_a?(ComplexType) and subs.length == 2 and !subs[0].is_a?(UniqueType) and !subs[1].is_a?(UniqueType)
|
52
52
|
# @todo should be able to resolve map; both types have it
|
53
53
|
# with same return type
|
54
54
|
# @sg-ignore
|
@@ -73,19 +73,63 @@ module Solargraph
|
|
73
73
|
end
|
74
74
|
raise "Please remove leading :: and set rooted instead - #{name.inspect}" if name.start_with?('::')
|
75
75
|
@name = name
|
76
|
-
@
|
77
|
-
|
76
|
+
@parameters_type = parameters_type
|
77
|
+
if implicit_union?
|
78
|
+
@key_types = key_types.uniq
|
79
|
+
@subtypes = subtypes.uniq
|
80
|
+
else
|
81
|
+
@key_types = key_types
|
82
|
+
@subtypes = subtypes
|
83
|
+
end
|
78
84
|
@rooted = rooted
|
79
85
|
@all_params = []
|
80
|
-
@all_params.concat key_types
|
81
|
-
@all_params.concat subtypes
|
82
|
-
|
86
|
+
@all_params.concat @key_types
|
87
|
+
@all_params.concat @subtypes
|
88
|
+
end
|
89
|
+
|
90
|
+
def implicit_union?
|
91
|
+
# @todo use api_map to establish number of generics in type;
|
92
|
+
# if only one is allowed but multiple are passed in, treat
|
93
|
+
# those as implicit unions
|
94
|
+
['Hash', 'Array', 'Set', '_ToAry', 'Enumerable', '_Each'].include?(name) && parameters_type != :fixed
|
83
95
|
end
|
84
96
|
|
85
97
|
def to_s
|
86
98
|
tag
|
87
99
|
end
|
88
100
|
|
101
|
+
def simplify_literals
|
102
|
+
transform do |t|
|
103
|
+
next t unless t.literal?
|
104
|
+
t.recreate(new_name: t.non_literal_name)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def literal?
|
109
|
+
non_literal_name != name
|
110
|
+
end
|
111
|
+
|
112
|
+
def non_literal_name
|
113
|
+
@non_literal_name ||= determine_non_literal_name
|
114
|
+
end
|
115
|
+
|
116
|
+
def determine_non_literal_name
|
117
|
+
# https://github.com/ruby/rbs/blob/master/docs/syntax.md
|
118
|
+
#
|
119
|
+
# _literal_ ::= _string-literal_
|
120
|
+
# | _symbol-literal_
|
121
|
+
# | _integer-literal_
|
122
|
+
# | `true`
|
123
|
+
# | `false`
|
124
|
+
return name if name.empty?
|
125
|
+
return 'NilClass' if name == 'nil'
|
126
|
+
return 'Boolean' if ['true', 'false'].include?(name)
|
127
|
+
return 'Symbol' if name[0] == ':'
|
128
|
+
return 'String' if ['"', "'"].include?(name[0])
|
129
|
+
return 'Integer' if name.match?(/^-?\d+$/)
|
130
|
+
name
|
131
|
+
end
|
132
|
+
|
89
133
|
def eql?(other)
|
90
134
|
self.class == other.class &&
|
91
135
|
@name == other.name &&
|
@@ -113,6 +157,8 @@ module Solargraph
|
|
113
157
|
def rbs_name
|
114
158
|
if name == 'undefined'
|
115
159
|
'untyped'
|
160
|
+
elsif literal?
|
161
|
+
name
|
116
162
|
else
|
117
163
|
rooted_name
|
118
164
|
end
|
@@ -183,6 +229,23 @@ module Solargraph
|
|
183
229
|
name == GENERIC_TAG_NAME || all_params.any?(&:generic?)
|
184
230
|
end
|
185
231
|
|
232
|
+
# @param api_map [ApiMap] The ApiMap that performs qualification
|
233
|
+
# @param atype [ComplexType] type which may be assigned to this type
|
234
|
+
def can_assign?(api_map, atype)
|
235
|
+
logger.debug { "UniqueType#can_assign?(self=#{rooted_tags.inspect}, atype=#{atype.rooted_tags.inspect})" }
|
236
|
+
downcasted_atype = atype.downcast_to_literal_if_possible
|
237
|
+
out = downcasted_atype.all? do |autype|
|
238
|
+
autype.name == name || api_map.super_and_sub?(name, autype.name)
|
239
|
+
end
|
240
|
+
logger.debug { "UniqueType#can_assign?(self=#{rooted_tags.inspect}, atype=#{atype.rooted_tags.inspect}) => #{out}" }
|
241
|
+
out
|
242
|
+
end
|
243
|
+
|
244
|
+
# @return [UniqueType]
|
245
|
+
def downcast_to_literal_if_possible
|
246
|
+
SINGLE_SUBTYPE.fetch(rooted_tag, self)
|
247
|
+
end
|
248
|
+
|
186
249
|
# @param generics_to_resolve [Enumerable<String>]
|
187
250
|
# @param context_type [UniqueType, nil]
|
188
251
|
# @param resolved_generic_values [Hash{String => ComplexType}] Added to as types are encountered or resolved
|
@@ -253,6 +316,12 @@ module Solargraph
|
|
253
316
|
else
|
254
317
|
next ComplexType::UNDEFINED
|
255
318
|
end
|
319
|
+
elsif context_type.all?(&:implicit_union?)
|
320
|
+
if idx == 0 && !context_type.all_params.empty?
|
321
|
+
ComplexType.new(context_type.all_params)
|
322
|
+
else
|
323
|
+
ComplexType::UNDEFINED
|
324
|
+
end
|
256
325
|
else
|
257
326
|
context_type.all_params[idx] || ComplexType::UNDEFINED
|
258
327
|
end
|
@@ -381,6 +450,18 @@ module Solargraph
|
|
381
450
|
|
382
451
|
UNDEFINED = UniqueType.new('undefined', rooted: false)
|
383
452
|
BOOLEAN = UniqueType.new('Boolean', rooted: true)
|
453
|
+
TRUE = UniqueType.new('true', rooted: true)
|
454
|
+
FALSE = UniqueType.new('false', rooted: true)
|
455
|
+
NIL = UniqueType.new('nil', rooted: true)
|
456
|
+
# @type [Hash{String => UniqueType}]
|
457
|
+
SINGLE_SUBTYPE = {
|
458
|
+
'::TrueClass' => UniqueType::TRUE,
|
459
|
+
'::FalseClass' => UniqueType::FALSE,
|
460
|
+
'::NilClass' => UniqueType::NIL
|
461
|
+
}.freeze
|
462
|
+
|
463
|
+
|
464
|
+
include Logging
|
384
465
|
end
|
385
466
|
end
|
386
467
|
end
|
@@ -16,7 +16,13 @@ module Solargraph
|
|
16
16
|
def initialize types = [UniqueType::UNDEFINED]
|
17
17
|
# @todo @items here should not need an annotation
|
18
18
|
# @type [Array<UniqueType>]
|
19
|
-
|
19
|
+
items = types.flat_map(&:items).uniq(&:to_s)
|
20
|
+
if items.any? { |i| i.name == 'false' } && items.any? { |i| i.name == 'true' }
|
21
|
+
items.delete_if { |i| i.name == 'false' || i.name == 'true' }
|
22
|
+
items.unshift(ComplexType::BOOLEAN)
|
23
|
+
end
|
24
|
+
items = [UniqueType::UNDEFINED] if items.any?(&:undefined?)
|
25
|
+
@items = items
|
20
26
|
end
|
21
27
|
|
22
28
|
# @sg-ignore Fix "Not enough arguments to Module#protected"
|
@@ -70,7 +76,7 @@ module Solargraph
|
|
70
76
|
end
|
71
77
|
|
72
78
|
# @yieldparam [UniqueType]
|
73
|
-
# @return [Array]
|
79
|
+
# @return [Array<UniqueType>]
|
74
80
|
def map &block
|
75
81
|
@items.map &block
|
76
82
|
end
|
@@ -93,6 +99,12 @@ module Solargraph
|
|
93
99
|
end
|
94
100
|
end
|
95
101
|
|
102
|
+
# @param atype [ComplexType] type which may be assigned to this type
|
103
|
+
# @param api_map [ApiMap] The ApiMap that performs qualification
|
104
|
+
def can_assign?(api_map, atype)
|
105
|
+
any? { |ut| ut.can_assign?(api_map, atype) }
|
106
|
+
end
|
107
|
+
|
96
108
|
# @return [Integer]
|
97
109
|
def length
|
98
110
|
@items.length
|
@@ -103,10 +115,6 @@ module Solargraph
|
|
103
115
|
@items
|
104
116
|
end
|
105
117
|
|
106
|
-
def tags
|
107
|
-
@items.map(&:tag).join(', ')
|
108
|
-
end
|
109
|
-
|
110
118
|
# @param index [Integer]
|
111
119
|
# @return [UniqueType]
|
112
120
|
def [](index)
|
@@ -147,6 +155,23 @@ module Solargraph
|
|
147
155
|
map(&:tag).join(', ')
|
148
156
|
end
|
149
157
|
|
158
|
+
def tags
|
159
|
+
map(&:tag).join(', ')
|
160
|
+
end
|
161
|
+
|
162
|
+
def simple_tags
|
163
|
+
simplify_literals.tags
|
164
|
+
end
|
165
|
+
|
166
|
+
def literal?
|
167
|
+
@items.any?(&:literal?)
|
168
|
+
end
|
169
|
+
|
170
|
+
# @return [ComplexType]
|
171
|
+
def downcast_to_literal_if_possible
|
172
|
+
ComplexType.new(items.map(&:downcast_to_literal_if_possible))
|
173
|
+
end
|
174
|
+
|
150
175
|
def desc
|
151
176
|
rooted_tags
|
152
177
|
end
|
@@ -175,6 +200,10 @@ module Solargraph
|
|
175
200
|
any?(&:generic?)
|
176
201
|
end
|
177
202
|
|
203
|
+
def simplify_literals
|
204
|
+
ComplexType.new(map(&:simplify_literals))
|
205
|
+
end
|
206
|
+
|
178
207
|
# @param new_name [String, nil]
|
179
208
|
# @yieldparam t [UniqueType]
|
180
209
|
# @yieldreturn [UniqueType]
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Solargraph
|
4
|
+
module Convention
|
5
|
+
module StructDefinition
|
6
|
+
# A node wrapper for a Struct definition via const assignment.
|
7
|
+
# @example
|
8
|
+
# MyStruct = Struct.new(:bar, :baz) do
|
9
|
+
# def foo
|
10
|
+
# end
|
11
|
+
# end
|
12
|
+
class StructAssignmentNode < StructDefintionNode
|
13
|
+
class << self
|
14
|
+
# @example
|
15
|
+
# s(:casgn, nil, :Foo,
|
16
|
+
# s(:block,
|
17
|
+
# s(:send,
|
18
|
+
# s(:const, nil, :Struct), :new,
|
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 valid?(node)
|
26
|
+
return false unless node&.type == :casgn
|
27
|
+
return false if node.children[2].nil?
|
28
|
+
struct_node = node.children[2].children[0]
|
29
|
+
|
30
|
+
struct_definition_node?(struct_node)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def class_name
|
35
|
+
if node.children[0]
|
36
|
+
Parser::NodeMethods.unpack_name(node.children[0]) + "::#{node.children[1]}"
|
37
|
+
else
|
38
|
+
node.children[1].to_s
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# @return [Parser::AST::Node]
|
45
|
+
def struct_node
|
46
|
+
node.children[2].children[0]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Solargraph
|
4
|
+
module Convention
|
5
|
+
module StructDefinition
|
6
|
+
# A node wrapper for a Struct definition via inheritance.
|
7
|
+
# @example
|
8
|
+
# class MyStruct < Struct.new(:bar, :baz)
|
9
|
+
# def foo
|
10
|
+
# end
|
11
|
+
# end
|
12
|
+
class StructDefintionNode
|
13
|
+
class << self
|
14
|
+
# @example
|
15
|
+
# s(:class,
|
16
|
+
# s(:const, nil, :Foo),
|
17
|
+
# s(:send,
|
18
|
+
# s(:const, nil, :Struct), :new,
|
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 valid?(node)
|
29
|
+
return false unless node&.type == :class
|
30
|
+
|
31
|
+
struct_definition_node?(node.children[1])
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# @param struct_node [Parser::AST::Node]
|
37
|
+
# @return [Boolean]
|
38
|
+
def struct_definition_node?(struct_node)
|
39
|
+
return false unless struct_node.is_a?(::Parser::AST::Node)
|
40
|
+
return false unless struct_node&.type == :send
|
41
|
+
return false unless struct_node.children[0]&.type == :const
|
42
|
+
return false unless struct_node.children[0].children[1] == :Struct
|
43
|
+
return false unless struct_node.children[1] == :new
|
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
|
+
struct_attribute_nodes.map do |struct_def_param|
|
62
|
+
next unless struct_def_param.type == :sym
|
63
|
+
[struct_def_param, struct_def_param.children[0].to_s]
|
64
|
+
end.compact
|
65
|
+
end
|
66
|
+
|
67
|
+
def keyword_init?
|
68
|
+
keyword_init_param = struct_attribute_nodes.find do |struct_def_param|
|
69
|
+
struct_def_param.type == :hash && struct_def_param.children[0].type == :pair &&
|
70
|
+
struct_def_param.children[0].children[0].children[0] == :keyword_init
|
71
|
+
end
|
72
|
+
|
73
|
+
return false if keyword_init_param.nil?
|
74
|
+
|
75
|
+
keyword_init_param.children[0].children[1].type == :true
|
76
|
+
end
|
77
|
+
|
78
|
+
# @return [Parser::AST::Node]
|
79
|
+
def body_node
|
80
|
+
node.children[2]
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# @return [Parser::AST::Node]
|
86
|
+
attr_reader :node
|
87
|
+
|
88
|
+
# @return [Parser::AST::Node]
|
89
|
+
def struct_node
|
90
|
+
node.children[1]
|
91
|
+
end
|
92
|
+
|
93
|
+
# @return [Array<Parser::AST::Node>]
|
94
|
+
def struct_attribute_nodes
|
95
|
+
struct_node.children[2..-1]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Solargraph
|
4
|
+
module Convention
|
5
|
+
module StructDefinition
|
6
|
+
autoload :StructDefintionNode, 'solargraph/convention/struct_definition/struct_definition_node'
|
7
|
+
autoload :StructAssignmentNode, 'solargraph/convention/struct_definition/struct_assignment_node'
|
8
|
+
|
9
|
+
module NodeProcessors
|
10
|
+
class StructNode < Parser::NodeProcessor::Base
|
11
|
+
def process
|
12
|
+
return if struct_definition_node.nil?
|
13
|
+
|
14
|
+
loc = get_node_location(node)
|
15
|
+
nspin = Solargraph::Pin::Namespace.new(
|
16
|
+
type: :class,
|
17
|
+
location: loc,
|
18
|
+
closure: region.closure,
|
19
|
+
name: struct_definition_node.class_name,
|
20
|
+
comments: comments_for(node),
|
21
|
+
visibility: :public,
|
22
|
+
gates: region.closure.gates.freeze
|
23
|
+
)
|
24
|
+
pins.push nspin
|
25
|
+
|
26
|
+
# define initialize method
|
27
|
+
initialize_method_pin = Pin::Method.new(
|
28
|
+
name: 'initialize',
|
29
|
+
parameters: [],
|
30
|
+
scope: :instance,
|
31
|
+
location: get_node_location(node),
|
32
|
+
closure: nspin,
|
33
|
+
visibility: :private,
|
34
|
+
comments: comments_for(node)
|
35
|
+
)
|
36
|
+
|
37
|
+
pins.push initialize_method_pin
|
38
|
+
|
39
|
+
struct_definition_node.attributes.map do |attribute_node, attribute_name|
|
40
|
+
initialize_method_pin.parameters.push(
|
41
|
+
Pin::Parameter.new(
|
42
|
+
name: attribute_name,
|
43
|
+
decl: struct_definition_node.keyword_init? ? :kwarg : :arg,
|
44
|
+
location: get_node_location(attribute_node),
|
45
|
+
closure: initialize_method_pin
|
46
|
+
)
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
# define attribute accessors and instance variables
|
51
|
+
struct_definition_node.attributes.each do |attribute_node, attribute_name|
|
52
|
+
[attribute_name, "#{attribute_name}="].each do |name|
|
53
|
+
method_pin = Pin::Method.new(
|
54
|
+
name: name,
|
55
|
+
parameters: [],
|
56
|
+
scope: :instance,
|
57
|
+
location: get_node_location(attribute_node),
|
58
|
+
closure: nspin,
|
59
|
+
comments: attribute_comments(attribute_node, attribute_name),
|
60
|
+
visibility: :public
|
61
|
+
)
|
62
|
+
|
63
|
+
pins.push method_pin
|
64
|
+
|
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))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
process_children region.update(closure: nspin, visibility: :public)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# @return [StructDefintionNode, nil]
|
79
|
+
def struct_definition_node
|
80
|
+
@struct_definition_node ||= if StructDefintionNode.valid?(node)
|
81
|
+
StructDefintionNode.new(node)
|
82
|
+
elsif StructAssignmentNode.valid?(node)
|
83
|
+
StructAssignmentNode.new(node)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
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?
|
92
|
+
|
93
|
+
struct_comments.split("\n").find do |row|
|
94
|
+
row.include?(attribute_name)
|
95
|
+
end&.gsub('@param', '@return')&.gsub(attribute_name, '')
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -10,6 +10,7 @@ module Solargraph
|
|
10
10
|
autoload :Gemfile, 'solargraph/convention/gemfile'
|
11
11
|
autoload :Gemspec, 'solargraph/convention/gemspec'
|
12
12
|
autoload :Rakefile, 'solargraph/convention/rakefile'
|
13
|
+
autoload :StructDefinition, 'solargraph/convention/struct_definition'
|
13
14
|
|
14
15
|
# @type [Set<Convention::Base>]
|
15
16
|
@@conventions = Set.new
|