solargraph 0.60.3 → 0.61.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/plugins.yml +1 -5
- data/.github/workflows/rspec.yml +3 -1
- data/CHANGELOG.md +26 -0
- data/Gemfile +0 -3
- data/lib/solargraph/api_map/index.rb +15 -2
- data/lib/solargraph/api_map/store.rb +13 -0
- data/lib/solargraph/api_map.rb +60 -81
- data/lib/solargraph/cache_dir.rb +58 -0
- data/lib/solargraph/collection/base.rb +50 -0
- data/lib/solargraph/collection/core.rb +17 -0
- data/lib/solargraph/collection/gem.rb +57 -0
- data/lib/solargraph/collection/rbs.rb +49 -0
- data/lib/solargraph/collection/stdlib.rb +25 -0
- data/lib/solargraph/collection.rb +31 -0
- data/lib/solargraph/complex_type.rb +18 -5
- data/lib/solargraph/convention.rb +5 -3
- data/lib/solargraph/diagnostics/require_not_found.rb +0 -4
- data/lib/solargraph/external.rb +159 -0
- data/lib/solargraph/language_server/message/text_document/definition.rb +4 -1
- data/lib/solargraph/language_server/message/text_document/hover.rb +1 -2
- data/lib/solargraph/library.rb +11 -24
- data/lib/solargraph/metagem.rb +80 -0
- data/lib/solargraph/parser/parser_gem/node_chainer.rb +22 -3
- data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +14 -5
- data/lib/solargraph/pin/closure.rb +17 -0
- data/lib/solargraph/pin/factory_parameter.rb +84 -0
- data/lib/solargraph/pin/method.rb +7 -4
- data/lib/solargraph/pin.rb +1 -0
- data/lib/solargraph/rbs_map/base.rb +31 -0
- data/lib/solargraph/rbs_map/conversions.rb +3 -1
- data/lib/solargraph/rbs_map/{core_map.rb → core.rb} +18 -39
- data/lib/solargraph/rbs_map/core_fills.rb +1 -1
- data/lib/solargraph/rbs_map/gem.rb +13 -0
- data/lib/solargraph/rbs_map/helpers.rb +60 -0
- data/lib/solargraph/rbs_map/path.rb +19 -0
- data/lib/solargraph/rbs_map/stdlib.rb +24 -0
- data/lib/solargraph/rbs_map.rb +7 -198
- data/lib/solargraph/repo.rb +177 -0
- data/lib/solargraph/shell.rb +49 -99
- data/lib/solargraph/source/chain/call.rb +190 -67
- data/lib/solargraph/source/chain/literal.rb +17 -17
- data/lib/solargraph/source/chain/parameter.rb +79 -0
- data/lib/solargraph/source/chain.rb +44 -5
- data/lib/solargraph/source/source_chainer.rb +40 -44
- data/lib/solargraph/source_map/clip.rb +5 -3
- data/lib/solargraph/source_map/mapper.rb +6 -2
- data/lib/solargraph/source_map.rb +10 -5
- data/lib/solargraph/type_checker/rules.rb +2 -1
- data/lib/solargraph/version.rb +1 -1
- data/lib/solargraph/workspace.rb +0 -1
- data/lib/solargraph/yard_map/cache.rb +1 -1
- data/lib/solargraph/yard_map/directives/attribute_directive.rb +1 -1
- data/lib/solargraph/yard_map/directives/domain_directive.rb +1 -1
- data/lib/solargraph/yard_map/directives/method_directive.rb +1 -1
- data/lib/solargraph/yard_map/directives/override_directive.rb +1 -1
- data/lib/solargraph/yard_map/directives/parse_directive.rb +1 -1
- data/lib/solargraph/yard_map/directives/scope_directive.rb +178 -0
- data/lib/solargraph/yard_map/directives/visibility_directive.rb +1 -1
- data/lib/solargraph/yard_map/directives.rb +5 -2
- data/lib/solargraph/yard_map/helpers.rb +1 -1
- data/lib/solargraph/yard_map/macro.rb +34 -5
- data/lib/solargraph/yard_map/mapper/to_constant.rb +1 -1
- data/lib/solargraph/yard_map/mapper/to_method.rb +1 -1
- data/lib/solargraph/yard_map/mapper/to_namespace.rb +1 -1
- data/lib/solargraph/yard_map/mapper.rb +37 -4
- data/lib/solargraph/yard_map.rb +1 -1
- data/lib/solargraph/yardoc.rb +43 -60
- data/lib/solargraph.rb +6 -3
- data/solargraph.gemspec +1 -1
- metadata +22 -9
- data/lib/solargraph/doc_map.rb +0 -439
- data/lib/solargraph/gem_pins.rb +0 -87
- data/lib/solargraph/pin_cache.rb +0 -248
- data/lib/solargraph/rbs_map/stdlib_map.rb +0 -71
- data/lib/solargraph/workspace/gemspecs.rb +0 -373
|
@@ -34,6 +34,7 @@ module Solargraph
|
|
|
34
34
|
autoload :ZSuper, 'solargraph/source/chain/z_super'
|
|
35
35
|
autoload :Hash, 'solargraph/source/chain/hash'
|
|
36
36
|
autoload :Array, 'solargraph/source/chain/array'
|
|
37
|
+
autoload :Parameter, 'solargraph/source/chain/parameter'
|
|
37
38
|
|
|
38
39
|
@@inference_stack = []
|
|
39
40
|
@@inference_depth = 0
|
|
@@ -168,7 +169,7 @@ module Solargraph
|
|
|
168
169
|
return ComplexType::UNDEFINED
|
|
169
170
|
end
|
|
170
171
|
type = infer_from_definitions(pins, links.last.last_context, api_map, locals)
|
|
171
|
-
out = maybe_nil(type)
|
|
172
|
+
out = maybe_nil(type, api_map)
|
|
172
173
|
logger.debug do
|
|
173
174
|
"Chain#infer_uncached(links=#{links.map(&:desc)}, locals=#{locals.map(&:desc)}, " \
|
|
174
175
|
"name_pin=#{name_pin}, name_pin.closure=#{name_pin&.closure&.inspect}, " \
|
|
@@ -182,6 +183,13 @@ module Solargraph
|
|
|
182
183
|
links.last.is_a?(Chain::Literal)
|
|
183
184
|
end
|
|
184
185
|
|
|
186
|
+
# @return [Boolean]
|
|
187
|
+
# @sg-ignore false "return type could not be inferred"
|
|
188
|
+
def require_parameter?
|
|
189
|
+
# @sg-ignore false "Unresolved call to require_parameter? on Solargraph::Source::Chain::Link"
|
|
190
|
+
links.last.is_a?(Chain::Parameter) && links.last.require_parameter?
|
|
191
|
+
end
|
|
192
|
+
|
|
185
193
|
def undefined?
|
|
186
194
|
links.any?(&:undefined?)
|
|
187
195
|
end
|
|
@@ -199,8 +207,38 @@ module Solargraph
|
|
|
199
207
|
@splat
|
|
200
208
|
end
|
|
201
209
|
|
|
202
|
-
|
|
203
|
-
|
|
210
|
+
# Whether this chain's inferred type should have nil added to it
|
|
211
|
+
# because a `&.` earlier in the chain might have short-circuited
|
|
212
|
+
# evaluation to nil.
|
|
213
|
+
#
|
|
214
|
+
# A `&.` only makes the *immediate* call nil-or-normal-result; a
|
|
215
|
+
# later, non-safe-navigated call in the same chain is still
|
|
216
|
+
# invoked on whatever that produced. If receiver is nil at that
|
|
217
|
+
# point, Ruby calls the method on nil itself instead of skipping
|
|
218
|
+
# it - so nil only continues to flow through subsequent links
|
|
219
|
+
# that NilClass defines as returning `self` (e.g., #tap,
|
|
220
|
+
# #itself). Any other subsequent call resolves to a concrete,
|
|
221
|
+
# non-nil-including type (e.g. NilClass#== returns Boolean), and
|
|
222
|
+
# nil no longer needs to be tracked past that point (unless a
|
|
223
|
+
# later `&.` reintroduces it).
|
|
224
|
+
#
|
|
225
|
+
# @param api_map [ApiMap]
|
|
226
|
+
# @return [Boolean]
|
|
227
|
+
def nullable? api_map
|
|
228
|
+
currently_nullable = false
|
|
229
|
+
links.each do |link|
|
|
230
|
+
if link.nullable?
|
|
231
|
+
currently_nullable = true
|
|
232
|
+
next
|
|
233
|
+
end
|
|
234
|
+
next unless currently_nullable
|
|
235
|
+
next unless link.is_a?(Chain::Call)
|
|
236
|
+
|
|
237
|
+
pin = api_map.get_method_stack('NilClass', link.word, scope: :instance).first
|
|
238
|
+
# @sg-ignore Need to add nil check here
|
|
239
|
+
currently_nullable = pin.nil? || pin.return_type.tag == 'self'
|
|
240
|
+
end
|
|
241
|
+
currently_nullable
|
|
204
242
|
end
|
|
205
243
|
|
|
206
244
|
include Logging
|
|
@@ -286,10 +324,11 @@ module Solargraph
|
|
|
286
324
|
end
|
|
287
325
|
|
|
288
326
|
# @param type [ComplexType, ComplexType::UniqueType]
|
|
327
|
+
# @param api_map [ApiMap]
|
|
289
328
|
# @return [ComplexType, ComplexType::UniqueType]
|
|
290
|
-
def maybe_nil type
|
|
329
|
+
def maybe_nil type, api_map
|
|
291
330
|
return type if type.undefined? || type.void? || type.nullable?
|
|
292
|
-
return type unless nullable?
|
|
331
|
+
return type unless nullable?(api_map)
|
|
293
332
|
ComplexType.new(type.items + [ComplexType::NIL])
|
|
294
333
|
end
|
|
295
334
|
|
|
@@ -31,55 +31,18 @@ module Solargraph
|
|
|
31
31
|
|
|
32
32
|
# @return [Source::Chain]
|
|
33
33
|
def chain
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
# return Chain.new([Chain::Literal.new('Integer', Integer(phrase[0..-2])),
|
|
37
|
-
# Chain::UNDEFINED_CALL])
|
|
38
|
-
# end
|
|
39
|
-
if phrase.start_with?(':') && !phrase.start_with?('::')
|
|
40
|
-
return Chain.new([Chain::Literal.new('Symbol',
|
|
41
|
-
# @sg-ignore Need to add nil check here
|
|
42
|
-
phrase[1..].to_sym)])
|
|
43
|
-
end
|
|
44
|
-
if end_of_phrase.strip == '::' && source.code[Position.to_offset(
|
|
45
|
-
source.code, position
|
|
46
|
-
)].to_s.match?(/[a-z]/i)
|
|
47
|
-
return SourceChainer.chain(source,
|
|
48
|
-
Position.new(position.line,
|
|
49
|
-
position.character + 1))
|
|
50
|
-
end
|
|
34
|
+
return SourceChainer.chain(source, Position.new(position.line, position.character + 1)) if end_of_phrase.strip == '::' && source.code[Position.to_offset(source.code, position)].to_s.match?(/[a-z]/i)
|
|
35
|
+
|
|
51
36
|
begin
|
|
52
|
-
|
|
53
|
-
# @type [::Parser::AST::Node, nil]
|
|
54
|
-
node = nil
|
|
55
|
-
# @type [::Parser::AST::Node, nil]
|
|
56
|
-
parent = nil
|
|
57
|
-
if !source.repaired? && source.parsed? && source.synchronized?
|
|
58
|
-
tree = source.tree_at(position.line, position.column)
|
|
59
|
-
node, parent = tree[0..2]
|
|
60
|
-
elsif source.parsed? && source.repaired? && end_of_phrase == '.'
|
|
61
|
-
node, parent = source.tree_at(fixed_position.line, fixed_position.column)[0..2]
|
|
62
|
-
# provide filename and line so that we can look up local variables there later
|
|
63
|
-
node = Parser.parse(fixed_phrase, source.filename, fixed_position.line) if node.nil?
|
|
64
|
-
elsif source.repaired?
|
|
65
|
-
node = Parser.parse(fixed_phrase, source.filename, fixed_position.line)
|
|
66
|
-
else
|
|
67
|
-
unless source.error_ranges.any? do |r|
|
|
68
|
-
r.nil? || r.include?(fixed_position)
|
|
69
|
-
end
|
|
70
|
-
node, parent = source.tree_at(fixed_position.line,
|
|
71
|
-
fixed_position.column)[0..2]
|
|
72
|
-
end
|
|
73
|
-
# Exception for positions that chain literal nodes in unsynchronized sources
|
|
74
|
-
node = nil unless source.synchronized? || !Parser.infer_literal_node_type(node).nil?
|
|
75
|
-
node = Parser.parse(fixed_phrase, source.filename, fixed_position.line) if node.nil?
|
|
76
|
-
end
|
|
37
|
+
node, parent, tree = node_from_position
|
|
77
38
|
rescue Parser::SyntaxError
|
|
78
39
|
return Chain.new([Chain::UNDEFINED_CALL])
|
|
79
40
|
end
|
|
41
|
+
|
|
80
42
|
return Chain.new([Chain::UNDEFINED_CALL]) if node.nil? || (node.type == :sym && !phrase.start_with?(':'))
|
|
81
|
-
|
|
82
|
-
chain = Parser.chain(node, source.filename, parent)
|
|
43
|
+
|
|
44
|
+
chain = Parser.chain(node, source.filename, parent, tree)
|
|
45
|
+
|
|
83
46
|
if source.repaired? || !source.parsed? || !source.synchronized?
|
|
84
47
|
if end_of_phrase.strip == '.'
|
|
85
48
|
chain.links.push Chain::UNDEFINED_CALL
|
|
@@ -94,6 +57,39 @@ module Solargraph
|
|
|
94
57
|
|
|
95
58
|
private
|
|
96
59
|
|
|
60
|
+
# @return [Array(Parser::AST::Node, Parser::AST::Node, Array<Parser::AST::Node>)]
|
|
61
|
+
# parent node, child node, and the tree at the position
|
|
62
|
+
def node_from_position
|
|
63
|
+
node = nil
|
|
64
|
+
parent = nil
|
|
65
|
+
tree = nil
|
|
66
|
+
|
|
67
|
+
if source.parsed? && !source.repaired? && source.synchronized?
|
|
68
|
+
tree = source.tree_at(position.line, position.column)
|
|
69
|
+
node, parent = tree[0..2]
|
|
70
|
+
elsif source.parsed? && source.repaired? && end_of_phrase == '.'
|
|
71
|
+
tree = source.tree_at(fixed_position.line, fixed_position.column)
|
|
72
|
+
node, parent = tree[0..2]
|
|
73
|
+
# provide filename and line so that we can look up local variables there later
|
|
74
|
+
node = Parser.parse(fixed_phrase, source.filename, fixed_position.line) if node.nil?
|
|
75
|
+
tree = source.tree_at(fixed_position.line, fixed_position.column) if node
|
|
76
|
+
elsif source.repaired?
|
|
77
|
+
node = Parser.parse(fixed_phrase, source.filename, fixed_position.line)
|
|
78
|
+
tree = source.tree_at(fixed_position.line, fixed_position.column) if node
|
|
79
|
+
else
|
|
80
|
+
unless source.error_ranges.any? { |r| r.nil? || r.include?(fixed_position) }
|
|
81
|
+
tree = source.tree_at(fixed_position.line, fixed_position.column)
|
|
82
|
+
node, parent = tree[0..2]
|
|
83
|
+
end
|
|
84
|
+
# Exception for positions that chain literal nodes in unsynchronized sources
|
|
85
|
+
node = nil unless source.synchronized? || !Parser.infer_literal_node_type(node).nil?
|
|
86
|
+
node = Parser.parse(fixed_phrase, source.filename, fixed_position.line) if node.nil?
|
|
87
|
+
tree = source.tree_at(fixed_position.line, fixed_position.column) if node
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
[node, parent, tree]
|
|
91
|
+
end
|
|
92
|
+
|
|
97
93
|
# @return [Position]
|
|
98
94
|
attr_reader :position
|
|
99
95
|
|
|
@@ -13,14 +13,14 @@ module Solargraph
|
|
|
13
13
|
@cursor = cursor
|
|
14
14
|
closure_pin = closure
|
|
15
15
|
# @sg-ignore Need to add nil check here
|
|
16
|
-
if closure_pin.is_a?(Pin::Block) && !Solargraph::Range.from_node(closure_pin.receiver)
|
|
16
|
+
if closure_pin.is_a?(Pin::Block) && !Solargraph::Range.from_node(closure_pin.receiver)&.contain?(cursor.range.start)
|
|
17
17
|
closure_pin.rebind(api_map)
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
# @return [Array<Pin::Base>] Relevant pins for infering the type of the Cursor's position
|
|
22
22
|
def define
|
|
23
|
-
return [] if cursor.comment? || cursor.chain.literal?
|
|
23
|
+
return [] if cursor.comment? || cursor.chain.literal? || cursor.chain.require_parameter?
|
|
24
24
|
result = cursor.chain.define(api_map, closure, locals)
|
|
25
25
|
result.concat file_global_methods
|
|
26
26
|
if result.empty?
|
|
@@ -40,7 +40,8 @@ module Solargraph
|
|
|
40
40
|
# @return [Completion]
|
|
41
41
|
def complete
|
|
42
42
|
return package_completions([]) if !source_map.source.parsed? || cursor.string?
|
|
43
|
-
|
|
43
|
+
# TODO: Improve magic word comparsion == '<::Symbol>', too fragile
|
|
44
|
+
if cursor.chain.literal? && cursor.chain.links.last.word == '<::Symbol>'
|
|
44
45
|
return package_completions(api_map.get_symbols)
|
|
45
46
|
end
|
|
46
47
|
return Completion.new([], cursor.range) if cursor.chain.literal?
|
|
@@ -211,6 +212,7 @@ module Solargraph
|
|
|
211
212
|
result.concat api_map.get_constants('', cursor.start_of_constant? ? '' : context_pin.full_context.namespace, *gates) # .select { |pin| pin.name.start_with?(full) }
|
|
212
213
|
end
|
|
213
214
|
else
|
|
215
|
+
type = type.qualify(api_map, *gates)
|
|
214
216
|
result.concat api_map.get_constants(type.namespace,
|
|
215
217
|
cursor.start_of_constant? ? '' : context_pin.full_context.namespace, *gates)
|
|
216
218
|
end
|
|
@@ -12,7 +12,7 @@ module Solargraph
|
|
|
12
12
|
|
|
13
13
|
private_class_method :new
|
|
14
14
|
|
|
15
|
-
DIRECTIVE_REGEXP = /(@!method|@!attribute|@!visibility|@!domain|@!macro|@!parse|@!override)/
|
|
15
|
+
DIRECTIVE_REGEXP = /(@!method|@!attribute|@!visibility|@!scope|@!domain|@!macro|@!parse|@!override)/
|
|
16
16
|
|
|
17
17
|
# Generate the data.
|
|
18
18
|
#
|
|
@@ -104,9 +104,13 @@ module Solargraph
|
|
|
104
104
|
directive_processor = YardMap::Directives.for(directive)
|
|
105
105
|
return unless directive_processor
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
new_pins = directive_processor.process_directive(
|
|
108
108
|
@source, @pins, source_position, comment_position, directive
|
|
109
109
|
)
|
|
110
|
+
# A @!scope directive in the indented text of another directive applies
|
|
111
|
+
# only to the object that directive defines.
|
|
112
|
+
YardMap::Directives::ScopeDirective.apply_nested directive, new_pins
|
|
113
|
+
@pins += new_pins
|
|
110
114
|
end
|
|
111
115
|
|
|
112
116
|
# @param comment [String]
|
|
@@ -190,10 +190,12 @@ module Solargraph
|
|
|
190
190
|
end
|
|
191
191
|
end
|
|
192
192
|
|
|
193
|
-
private
|
|
194
|
-
|
|
195
193
|
# @return [Array<Pin::Base>]
|
|
196
|
-
|
|
194
|
+
def convention_pins
|
|
195
|
+
@convention_pins || []
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
private
|
|
197
199
|
|
|
198
200
|
# @return [Hash{Class<Pin::Base> => Array<Pin::Base>}]
|
|
199
201
|
def pin_class_hash
|
|
@@ -206,9 +208,12 @@ module Solargraph
|
|
|
206
208
|
@data ||= Data.new(source)
|
|
207
209
|
end
|
|
208
210
|
|
|
211
|
+
# @param pins [Array<Pin::Base>]
|
|
209
212
|
# @return [Array<Pin::Base>]
|
|
210
|
-
def convention_pins
|
|
211
|
-
|
|
213
|
+
def convention_pins= pins
|
|
214
|
+
# unmemoizing the document_symbols in case it was called from any of conventions
|
|
215
|
+
@document_symbols = nil
|
|
216
|
+
@convention_pins = pins
|
|
212
217
|
end
|
|
213
218
|
|
|
214
219
|
# @generic T
|
|
@@ -75,7 +75,7 @@ module Solargraph
|
|
|
75
75
|
# @todo 22: Translate to something flow sensitive typing understands
|
|
76
76
|
# @todo 3: Need a downcast here
|
|
77
77
|
#
|
|
78
|
-
# flow sensitive typing could handle (
|
|
78
|
+
# flow sensitive typing could handle (97):
|
|
79
79
|
#
|
|
80
80
|
# @todo 36: flow sensitive typing needs to handle attrs
|
|
81
81
|
# @todo 29: flow sensitive typing should be able to handle redefinition
|
|
@@ -113,6 +113,7 @@ module Solargraph
|
|
|
113
113
|
# @todo 1: flow sensitive typing needs to create separate ranges for postfix if
|
|
114
114
|
# @todo 1: flow sensitive typing needs to handle constants
|
|
115
115
|
# @todo 1: flow sensitive typing needs to eliminate literal from union with return if foo == :bar
|
|
116
|
+
# @todo 1: Array#flatten returns a bare Array
|
|
116
117
|
def require_all_unique_types_match_expected?
|
|
117
118
|
report?(:require_all_unique_types_match_expected, :strong)
|
|
118
119
|
end
|
data/lib/solargraph/version.rb
CHANGED
data/lib/solargraph/workspace.rb
CHANGED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Solargraph
|
|
4
|
+
module YardMap
|
|
5
|
+
module Directives
|
|
6
|
+
# Implements YARD's @!scope directive.
|
|
7
|
+
#
|
|
8
|
+
# As described in YARD::Tags::ScopeDirective, a @!scope directive that
|
|
9
|
+
# appears in a docstring attached to a method definition applies only to
|
|
10
|
+
# that method. Anywhere else it applies to all of the methods that follow
|
|
11
|
+
# it within the namespace it was defined in.
|
|
12
|
+
#
|
|
13
|
+
# class Example
|
|
14
|
+
# # @!scope class
|
|
15
|
+
# def foo; end # => Example.foo
|
|
16
|
+
#
|
|
17
|
+
# def bar; end # => Example#bar
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# class Other
|
|
21
|
+
# # @!scope class
|
|
22
|
+
#
|
|
23
|
+
# def foo; end # => Other.foo
|
|
24
|
+
#
|
|
25
|
+
# def bar; end # => Other.bar
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
module ScopeDirective
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
VALID_SCOPES = %i[class instance].freeze
|
|
32
|
+
|
|
33
|
+
# Directives that define an object whose scope is inherited from a
|
|
34
|
+
# @!scope directive. @!parse is not one of them because the code it
|
|
35
|
+
# contains already declares the scope of everything it defines.
|
|
36
|
+
SCOPED_DIRECTIVES = %w[method attribute].freeze
|
|
37
|
+
|
|
38
|
+
# @param source [Solargraph::Source]
|
|
39
|
+
# @param pins [Array<Solargraph::Pin::Base>]
|
|
40
|
+
# @param source_position [Position]
|
|
41
|
+
# @param comment_position [Position]
|
|
42
|
+
# @param directive [YARD::Tags::Directive]
|
|
43
|
+
# @return [Array<Solargraph::Pin::Base>]
|
|
44
|
+
def process_directive source, pins, source_position, comment_position, directive
|
|
45
|
+
kind = parse_scope directive
|
|
46
|
+
return [] if kind.nil?
|
|
47
|
+
|
|
48
|
+
attached = attached_method pins, source_position
|
|
49
|
+
if attached && no_empty_lines?(source.code, comment_position.line, source_position.line)
|
|
50
|
+
attached.scope = kind
|
|
51
|
+
else
|
|
52
|
+
apply_to_namespace pins, kind, source_position
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
[]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @param directive [YARD::Tags::Directive]
|
|
59
|
+
# @return [::Symbol, nil] :class, :instance, or nil if the directive is malformed
|
|
60
|
+
def parse_scope directive
|
|
61
|
+
kind = directive.tag.text.to_s.strip.downcase.to_sym
|
|
62
|
+
VALID_SCOPES.include?(kind) ? kind : nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# True if the scope of the object defined by this directive can be set
|
|
66
|
+
# by a @!scope directive.
|
|
67
|
+
#
|
|
68
|
+
# @param directive [YARD::Tags::Directive]
|
|
69
|
+
# @return [Boolean]
|
|
70
|
+
def scopes_directive? directive
|
|
71
|
+
SCOPED_DIRECTIVES.include? directive.tag.tag_name
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# The scope set by a @!scope directive that appears in the indented
|
|
75
|
+
# text of another directive:
|
|
76
|
+
#
|
|
77
|
+
# @!method foo
|
|
78
|
+
# @!scope class
|
|
79
|
+
#
|
|
80
|
+
# YARD parses the indented text of a directive with a state of its own,
|
|
81
|
+
# so a directive nested this way applies only to the object the
|
|
82
|
+
# enclosing directive defines. It never affects the objects that
|
|
83
|
+
# follow.
|
|
84
|
+
#
|
|
85
|
+
# @param directive [YARD::Tags::Directive]
|
|
86
|
+
# @return [::Symbol, nil] :class, :instance, or nil if there is none
|
|
87
|
+
def nested_scope directive
|
|
88
|
+
text = directive.tag.text.to_s
|
|
89
|
+
return nil unless text.include? '@!scope'
|
|
90
|
+
|
|
91
|
+
kind = nil
|
|
92
|
+
Solargraph::Source.parse_docstring(text).directives.each do |nested|
|
|
93
|
+
next unless nested.tag.tag_name == 'scope'
|
|
94
|
+
|
|
95
|
+
# A later directive overrides an earlier one, but an invalid value
|
|
96
|
+
# leaves the current one in place, as it does in YARD.
|
|
97
|
+
kind = parse_scope(nested) || kind
|
|
98
|
+
end
|
|
99
|
+
kind
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Set the scope of pins generated by a directive.
|
|
103
|
+
#
|
|
104
|
+
# @param pins [Array<Solargraph::Pin::Base>]
|
|
105
|
+
# @param kind [::Symbol, nil] :class or :instance; nothing happens if nil
|
|
106
|
+
# @return [void]
|
|
107
|
+
def apply_scope pins, kind
|
|
108
|
+
return if kind.nil?
|
|
109
|
+
|
|
110
|
+
# Only methods have a scope. Applying one to any other kind of pin,
|
|
111
|
+
# such as a namespace generated by @!parse, would corrupt its path.
|
|
112
|
+
pins.each { |pin| pin.scope = kind if pin.is_a?(Pin::Method) }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Apply a @!scope directive from the indented text of another directive
|
|
116
|
+
# to the pins that directive generated.
|
|
117
|
+
#
|
|
118
|
+
# @param directive [YARD::Tags::Directive]
|
|
119
|
+
# @param pins [Array<Solargraph::Pin::Base>] the pins generated by the directive
|
|
120
|
+
# @return [void]
|
|
121
|
+
def apply_nested directive, pins
|
|
122
|
+
return unless scopes_directive? directive
|
|
123
|
+
|
|
124
|
+
apply_scope pins, nested_scope(directive)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Applies the scope to each method that follows the directive within
|
|
128
|
+
# the namespace the directive appears in. Methods in nested namespaces
|
|
129
|
+
# are excluded, since the directive only affects its own namespace.
|
|
130
|
+
#
|
|
131
|
+
# @param pins [Array<Solargraph::Pin::Base>]
|
|
132
|
+
# @param kind [::Symbol] :class or :instance
|
|
133
|
+
# @param source_position [Position]
|
|
134
|
+
# @return [void]
|
|
135
|
+
def apply_to_namespace pins, kind, source_position
|
|
136
|
+
namespace = namespace_at pins, source_position
|
|
137
|
+
return if namespace.nil?
|
|
138
|
+
|
|
139
|
+
pins.each do |pin|
|
|
140
|
+
next unless pin.is_a?(Pin::Method)
|
|
141
|
+
next unless pin.namespace == namespace.path
|
|
142
|
+
# @sg-ignore flow sensitive typing should narrow location after the nil check
|
|
143
|
+
next if pin.location.nil? || pin.location.range.start.line < source_position.line
|
|
144
|
+
|
|
145
|
+
pin.scope = kind
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# The method defined on the line the comment is attached to, if any.
|
|
150
|
+
#
|
|
151
|
+
# @param pins [Array<Solargraph::Pin::Base>]
|
|
152
|
+
# @param source_position [Position]
|
|
153
|
+
# @return [Pin::Method, nil]
|
|
154
|
+
def attached_method pins, source_position
|
|
155
|
+
pins.select { |pin| pin.is_a?(Pin::Method) && pin.location&.range&.contain?(source_position) }.last
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# @param pins [Array<Solargraph::Pin::Base>]
|
|
159
|
+
# @param position [Position]
|
|
160
|
+
# @return [Pin::Namespace, nil]
|
|
161
|
+
def namespace_at pins, position
|
|
162
|
+
pins.select { |pin| pin.is_a?(Pin::Namespace) && pin.location&.range&.contain?(position) }.last
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# @param code [String]
|
|
166
|
+
# @param line1 [Integer]
|
|
167
|
+
# @param line2 [Integer]
|
|
168
|
+
# @return [Boolean]
|
|
169
|
+
def no_empty_lines? code, line1, line2
|
|
170
|
+
lines = code.lines[line1..line2]
|
|
171
|
+
return false if lines.nil?
|
|
172
|
+
|
|
173
|
+
lines.none? { |line| line.strip.empty? }
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Solargraph
|
|
4
|
-
|
|
4
|
+
module YardMap
|
|
5
5
|
module Directives
|
|
6
6
|
autoload :AttributeDirective, 'solargraph/yard_map/directives/attribute_directive'
|
|
7
7
|
autoload :MethodDirective, 'solargraph/yard_map/directives/method_directive'
|
|
8
8
|
autoload :DomainDirective, 'solargraph/yard_map/directives/domain_directive'
|
|
9
9
|
autoload :OverrideDirective, 'solargraph/yard_map/directives/override_directive'
|
|
10
10
|
autoload :ParseDirective, 'solargraph/yard_map/directives/parse_directive'
|
|
11
|
+
autoload :ScopeDirective, 'solargraph/yard_map/directives/scope_directive'
|
|
11
12
|
autoload :VisibilityDirective, 'solargraph/yard_map/directives/visibility_directive'
|
|
12
13
|
|
|
13
14
|
# @param directive [YARD::Tags::Directive]
|
|
14
|
-
# @return [Class<AttributeDirective>, Class<MethodDirective>, Class<DomainDirective>, Class<OverrideDirective>, Class<ParseDirective>, Class<VisibilityDirective>, nil]
|
|
15
|
+
# @return [Class<AttributeDirective>, Class<MethodDirective>, Class<DomainDirective>, Class<OverrideDirective>, Class<ParseDirective>, Class<ScopeDirective>, Class<VisibilityDirective>, nil]
|
|
15
16
|
def self.for directive
|
|
16
17
|
case directive.tag.tag_name
|
|
17
18
|
when 'attribute'
|
|
@@ -24,6 +25,8 @@ module Solargraph
|
|
|
24
25
|
OverrideDirective
|
|
25
26
|
when 'parse'
|
|
26
27
|
ParseDirective
|
|
28
|
+
when 'scope'
|
|
29
|
+
ScopeDirective
|
|
27
30
|
when 'visibility'
|
|
28
31
|
VisibilityDirective
|
|
29
32
|
else # rubocop:disable Style/EmptyElse
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Solargraph
|
|
4
|
-
|
|
4
|
+
module YardMap
|
|
5
5
|
class Macro
|
|
6
|
-
PROCESSABLE_DIRECTIVES = %w[method attribute parse].freeze
|
|
6
|
+
PROCESSABLE_DIRECTIVES = %w[method attribute parse scope].freeze
|
|
7
|
+
|
|
8
|
+
# Directives whose text is a value rather than a definition. The comments
|
|
9
|
+
# attached to the macro's call site belong to the objects the macro
|
|
10
|
+
# generates, but adding them to one of these directives would invalidate
|
|
11
|
+
# its content.
|
|
12
|
+
VALUE_DIRECTIVES = %w[parse scope].freeze
|
|
7
13
|
|
|
8
14
|
class << self
|
|
9
15
|
# @param directive [YARD::Tags::Directive]
|
|
@@ -73,13 +79,34 @@ module Solargraph
|
|
|
73
79
|
# @return [Array<Pin::Base>]
|
|
74
80
|
def generate_pins_from chain, pin, source_map
|
|
75
81
|
call_location = Solargraph::Location.from_node(chain.node)
|
|
82
|
+
return [] unless call_location
|
|
83
|
+
|
|
84
|
+
# A @!scope directive in a macro applies to the objects the macro
|
|
85
|
+
# generates after it, the same way it applies to the objects that
|
|
86
|
+
# follow it in a namespace. Its effect ends with the macro.
|
|
87
|
+
#
|
|
88
|
+
# @type [::Symbol, nil]
|
|
89
|
+
inherited_scope = nil
|
|
90
|
+
|
|
76
91
|
# @param generated_pins [Array<Pin::Base>]
|
|
77
92
|
generate_yardoc_from(chain, source_map).reduce([]) do |generated_pins, directive|
|
|
93
|
+
# @sg-ignore flow sensitive typing confuses this block variable with the #directive attr reader
|
|
94
|
+
if directive.tag.tag_name == 'scope'
|
|
95
|
+
inherited_scope = Directives::ScopeDirective.parse_scope(directive) || inherited_scope
|
|
96
|
+
next generated_pins
|
|
97
|
+
end
|
|
78
98
|
directive_processor = YardMap::Directives.for(directive)
|
|
79
|
-
next generated_pins unless directive_processor
|
|
80
|
-
|
|
99
|
+
next generated_pins unless directive_processor
|
|
100
|
+
new_pins = directive_processor.process_directive(
|
|
81
101
|
source_map.source, source_map.pins, call_location.range.start, call_location.range.start, directive
|
|
82
102
|
)
|
|
103
|
+
if Directives::ScopeDirective.scopes_directive?(directive)
|
|
104
|
+
# A @!scope directive nested in another directive's text applies
|
|
105
|
+
# only to the object that directive defines.
|
|
106
|
+
scope = Directives::ScopeDirective.nested_scope(directive) || inherited_scope
|
|
107
|
+
Directives::ScopeDirective.apply_scope new_pins, scope
|
|
108
|
+
end
|
|
109
|
+
generated_pins + new_pins
|
|
83
110
|
end
|
|
84
111
|
end
|
|
85
112
|
|
|
@@ -100,9 +127,11 @@ module Solargraph
|
|
|
100
127
|
PROCESSABLE_DIRECTIVES.include?(directive.tag.tag_name)
|
|
101
128
|
end
|
|
102
129
|
directives.each do |directive|
|
|
130
|
+
next if VALUE_DIRECTIVES.include? directive.tag.tag_name
|
|
131
|
+
|
|
103
132
|
# @sg-ignore chain.node is assumed to exist
|
|
104
133
|
comments = source_map.source.comments_for(chain.node)
|
|
105
|
-
if comments&.length&.positive?
|
|
134
|
+
if comments&.length&.positive?
|
|
106
135
|
directive.tag.text += "\n#{comments}"
|
|
107
136
|
end
|
|
108
137
|
end
|