solargraph 0.59.2 → 0.60.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/rspec.yml +4 -1
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +1 -1
- data/CHANGELOG.md +11 -0
- data/Gemfile +3 -0
- data/lib/solargraph/api_map/index.rb +13 -2
- data/lib/solargraph/api_map/store.rb +21 -6
- data/lib/solargraph/api_map.rb +34 -2
- data/lib/solargraph/complex_type/unique_type.rb +4 -0
- data/lib/solargraph/complex_type.rb +4 -0
- data/lib/solargraph/doc_map.rb +1 -0
- data/lib/solargraph/parser/parser_gem/node_methods.rb +42 -0
- data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +29 -5
- data/lib/solargraph/pin/base.rb +31 -3
- data/lib/solargraph/pin/callable.rb +2 -2
- data/lib/solargraph/pin/common.rb +12 -0
- data/lib/solargraph/pin/method.rb +56 -16
- data/lib/solargraph/rbs_map/conversions.rb +96 -145
- data/lib/solargraph/rbs_translator.rb +206 -0
- data/lib/solargraph/shell.rb +130 -63
- data/lib/solargraph/source/chain/call.rb +8 -76
- data/lib/solargraph/source_map/mapper.rb +5 -135
- data/lib/solargraph/source_map.rb +14 -0
- data/lib/solargraph/version.rb +19 -1
- data/lib/solargraph/yard_map/directives/attribute_directive.rb +65 -0
- data/lib/solargraph/yard_map/directives/domain_directive.rb +30 -0
- data/lib/solargraph/yard_map/directives/method_directive.rb +51 -0
- data/lib/solargraph/yard_map/directives/override_directive.rb +30 -0
- data/lib/solargraph/yard_map/directives/parse_directive.rb +53 -0
- data/lib/solargraph/yard_map/directives/visibility_directive.rb +70 -0
- data/lib/solargraph/yard_map/directives.rb +35 -0
- data/lib/solargraph/yard_map/macro.rb +113 -0
- data/lib/solargraph/yard_map/mapper.rb +19 -1
- data/lib/solargraph/yard_map.rb +2 -0
- data/lib/solargraph.rb +1 -0
- data/solargraph.gemspec +1 -0
- metadata +24 -1
data/lib/solargraph/shell.rb
CHANGED
|
@@ -4,10 +4,14 @@ require 'benchmark'
|
|
|
4
4
|
require 'thor'
|
|
5
5
|
require 'yard'
|
|
6
6
|
require 'yaml'
|
|
7
|
+
require 'sord'
|
|
8
|
+
require 'tmpdir'
|
|
9
|
+
|
|
7
10
|
|
|
8
11
|
module Solargraph
|
|
9
12
|
class Shell < Thor
|
|
10
13
|
include Solargraph::ServerMethods
|
|
14
|
+
include ApiMap::SourceToYard
|
|
11
15
|
|
|
12
16
|
# Tell Thor to ensure the process exits with status 1 if any error happens.
|
|
13
17
|
def self.exit_on_failure?
|
|
@@ -112,12 +116,10 @@ module Solargraph
|
|
|
112
116
|
PinCache.serialize_yard_gem(gemspec, pins)
|
|
113
117
|
end
|
|
114
118
|
|
|
115
|
-
workspace = Solargraph::Workspace.new(Dir.pwd)
|
|
116
|
-
rbs_map = RbsMap.from_gemspec(gemspec, workspace
|
|
119
|
+
workspace = Solargraph::Workspace.new(Dir.pwd) if File.exist?('rbs_collection.yaml')
|
|
120
|
+
rbs_map = RbsMap.from_gemspec(gemspec, workspace&.rbs_collection_path, workspace&.rbs_collection_config_path)
|
|
117
121
|
if options[:rebuild] || !PinCache.has_rbs_collection?(gemspec, rbs_map.cache_key)
|
|
118
|
-
|
|
119
|
-
pins = rbs_map.pins || []
|
|
120
|
-
PinCache.serialize_rbs_collection_gem(gemspec, rbs_map.cache_key, pins)
|
|
122
|
+
PinCache.serialize_rbs_collection_gem(gemspec, rbs_map.cache_key, rbs_map.pins)
|
|
121
123
|
end
|
|
122
124
|
rescue Gem::MissingSpecError
|
|
123
125
|
warn "Gem '#{gem}' not found"
|
|
@@ -261,7 +263,7 @@ module Solargraph
|
|
|
261
263
|
next if problems.empty?
|
|
262
264
|
problems.sort! { |a, b| a.location.range.start.line <=> b.location.range.start.line }
|
|
263
265
|
puts problems.map { |prob|
|
|
264
|
-
"#{prob.location.filename}:#{prob.location.range.start.line + 1}
|
|
266
|
+
"#{prob.location.filename}:#{prob.location.range.start.line + 1}: #{prob.message}"
|
|
265
267
|
}.join("\n")
|
|
266
268
|
filecount += 1
|
|
267
269
|
probcount += problems.length
|
|
@@ -420,74 +422,139 @@ module Solargraph
|
|
|
420
422
|
puts "Notification: #{method} - #{params}"
|
|
421
423
|
end
|
|
422
424
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
425
|
+
parse_path = File.join(options[:output_dir], 'parse_benchmark.json.gz')
|
|
426
|
+
catalog_path = File.join(options[:output_dir], 'catalog_benchmark.json.gz')
|
|
427
|
+
definition_path = File.join(options[:output_dir], 'definition_benchmark.json.gz')
|
|
428
|
+
|
|
429
|
+
prepare_time = catalog_time = definition_time = nil
|
|
430
|
+
|
|
431
|
+
# Trap CTRL-C so the in-progress Vernier.profile block can unwind through
|
|
432
|
+
# its ensure clause and still write its output file. A second CTRL-C
|
|
433
|
+
# restores default handling for a hard exit.
|
|
434
|
+
interrupted = false
|
|
435
|
+
previous_int_trap = Signal.trap('INT') do
|
|
436
|
+
if interrupted
|
|
437
|
+
Signal.trap('INT', 'DEFAULT')
|
|
438
|
+
Process.kill('INT', Process.pid)
|
|
439
|
+
else
|
|
440
|
+
interrupted = true
|
|
441
|
+
puts "\nInterrupted. Finishing current profile (CTRL-C again to force exit)..."
|
|
442
|
+
raise Interrupt
|
|
443
|
+
end
|
|
429
444
|
end
|
|
430
|
-
prepare_time = Time.now - prepare_start
|
|
431
445
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
446
|
+
begin
|
|
447
|
+
puts 'Parsing and mapping source files...'
|
|
448
|
+
prepare_start = Time.now
|
|
449
|
+
Vernier.profile(out: parse_path, hooks: hooks) do
|
|
450
|
+
puts 'Mapping libraries'
|
|
451
|
+
host.prepare(directory)
|
|
452
|
+
sleep 0.2 until host.libraries.all?(&:mapped?)
|
|
453
|
+
end
|
|
454
|
+
prepare_time = Time.now - prepare_start
|
|
438
455
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
456
|
+
puts 'Building the catalog...'
|
|
457
|
+
catalog_start = Time.now
|
|
458
|
+
Vernier.profile(out: catalog_path, hooks: hooks) do
|
|
459
|
+
host.catalog
|
|
460
|
+
end
|
|
461
|
+
catalog_time = Time.now - catalog_start
|
|
462
|
+
|
|
463
|
+
# Determine test file
|
|
464
|
+
if file
|
|
465
|
+
test_file = File.join(directory, file)
|
|
466
|
+
else
|
|
467
|
+
test_file = File.join(directory, 'lib', 'other.rb')
|
|
468
|
+
unless File.exist?(test_file)
|
|
469
|
+
# Fallback to any Ruby file in the workspace
|
|
470
|
+
workspace = Solargraph::Workspace.new(directory)
|
|
471
|
+
test_file = workspace.filenames.find { |f| f.end_with?('.rb') }
|
|
472
|
+
unless test_file
|
|
473
|
+
puts 'No Ruby files found in workspace'
|
|
474
|
+
return
|
|
475
|
+
end
|
|
451
476
|
end
|
|
452
477
|
end
|
|
453
|
-
end
|
|
454
478
|
|
|
455
|
-
|
|
479
|
+
file_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(File.absolute_path(test_file))
|
|
456
480
|
|
|
457
|
-
|
|
458
|
-
|
|
481
|
+
puts "Profiling go-to-definition for #{test_file}"
|
|
482
|
+
puts "Position: line #{options[:line]}, column #{options[:column]}"
|
|
459
483
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
484
|
+
definition_start = Time.now
|
|
485
|
+
Vernier.profile(out: definition_path, hooks: hooks) do
|
|
486
|
+
message = Solargraph::LanguageServer::Message::TextDocument::Definition.new(
|
|
487
|
+
host, {
|
|
488
|
+
'params' => {
|
|
489
|
+
'textDocument' => { 'uri' => file_uri },
|
|
490
|
+
'position' => { 'line' => options[:line], 'character' => options[:column] }
|
|
491
|
+
}
|
|
467
492
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
493
|
+
)
|
|
494
|
+
puts 'Processing go-to-definition request...'
|
|
495
|
+
result = message.process
|
|
496
|
+
|
|
497
|
+
puts "Result: #{result.inspect}"
|
|
498
|
+
end
|
|
499
|
+
definition_time = Time.now - definition_start
|
|
500
|
+
rescue Interrupt
|
|
501
|
+
puts "\nProfile run interrupted; partial profile(s) have been written."
|
|
502
|
+
ensure
|
|
503
|
+
Signal.trap('INT', previous_int_trap || 'DEFAULT')
|
|
504
|
+
|
|
505
|
+
puts "\n=== Timing Results ==="
|
|
506
|
+
puts "Parsing & mapping: #{(prepare_time * 1000).round(2)}ms" if prepare_time
|
|
507
|
+
puts "Catalog building: #{(catalog_time * 1000).round(2)}ms" if catalog_time
|
|
508
|
+
puts "Go-to-definition: #{(definition_time * 1000).round(2)}ms" if definition_time
|
|
509
|
+
if prepare_time && catalog_time && definition_time
|
|
510
|
+
total_time = prepare_time + catalog_time + definition_time
|
|
511
|
+
puts "Total time: #{(total_time * 1000).round(2)}ms"
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
saved = [parse_path, catalog_path, definition_path].select { |p| File.exist?(p) }
|
|
515
|
+
unless saved.empty?
|
|
516
|
+
puts "\nProfiles saved to:"
|
|
517
|
+
saved.each { |p| puts " - #{File.expand_path(p)}" }
|
|
518
|
+
|
|
519
|
+
puts "\nUpload the JSON files to https://vernier.prof/ to view the profiles."
|
|
520
|
+
puts 'Or use https://rubygems.org/gems/profile-viewer to view them locally.'
|
|
521
|
+
end
|
|
522
|
+
end
|
|
523
|
+
end
|
|
472
524
|
|
|
473
|
-
|
|
525
|
+
desc 'rbs', 'Generate RBS definitions'
|
|
526
|
+
option :filename, type: :string, alias: :f, desc: 'Generated file name', default: 'sig.rbs'
|
|
527
|
+
option :inference, type: :boolean, desc: 'Enhance definitions with type inference', default: true
|
|
528
|
+
def rbs
|
|
529
|
+
api_map = Solargraph::ApiMap.load('.')
|
|
530
|
+
pins = api_map.source_maps.flat_map(&:pins)
|
|
531
|
+
store = Solargraph::ApiMap::Store.new(pins)
|
|
532
|
+
if options[:inference]
|
|
533
|
+
puts 'Inferring untyped methods...'
|
|
534
|
+
store.method_pins.each do |pin|
|
|
535
|
+
next unless pin.return_type.undefined?
|
|
536
|
+
type = pin.typify(api_map)
|
|
537
|
+
type = pin.probe(api_map) if type.undefined?
|
|
538
|
+
pin.docstring.add_tag YARD::Tags::Tag.new('return', nil, type.items.map(&:to_s))
|
|
539
|
+
pin.instance_variable_set(:@return_type, type)
|
|
540
|
+
end
|
|
541
|
+
end
|
|
542
|
+
puts 'Generating yardocs...'
|
|
543
|
+
rake_yard(store)
|
|
544
|
+
work_dir = Dir.pwd
|
|
545
|
+
Dir.mktmpdir do |tmpdir|
|
|
546
|
+
Dir.chdir tmpdir do
|
|
547
|
+
yardoc = File.join(tmpdir, '.yardoc')
|
|
548
|
+
YARD::Registry.save(false, yardoc)
|
|
549
|
+
YARD::Registry.load(yardoc)
|
|
550
|
+
rel_dir = File.join('sig', options[:filename])
|
|
551
|
+
puts "Writing #{rel_dir}..."
|
|
552
|
+
target = File.join(work_dir, rel_dir)
|
|
553
|
+
FileUtils.mkdir_p(File.join(work_dir, 'sig'))
|
|
554
|
+
`sord #{target} --rbs --no-regenerate`
|
|
555
|
+
end
|
|
474
556
|
end
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
puts "\n=== Timing Results ==="
|
|
478
|
-
puts "Parsing & mapping: #{(prepare_time * 1000).round(2)}ms"
|
|
479
|
-
puts "Catalog building: #{(catalog_time * 1000).round(2)}ms"
|
|
480
|
-
puts "Go-to-definition: #{(definition_time * 1000).round(2)}ms"
|
|
481
|
-
total_time = prepare_time + catalog_time + definition_time
|
|
482
|
-
puts "Total time: #{(total_time * 1000).round(2)}ms"
|
|
483
|
-
|
|
484
|
-
puts "\nProfiles saved to:"
|
|
485
|
-
puts " - #{File.expand_path('parse_benchmark.json.gz', options[:output_dir])}"
|
|
486
|
-
puts " - #{File.expand_path('catalog_benchmark.json.gz', options[:output_dir])}"
|
|
487
|
-
puts " - #{File.expand_path('definition_benchmark.json.gz', options[:output_dir])}"
|
|
488
|
-
|
|
489
|
-
puts "\nUpload the JSON files to https://vernier.prof/ to view the profiles."
|
|
490
|
-
puts 'Or use https://rubygems.org/gems/profile-viewer to view them locally.'
|
|
557
|
+
puts 'Done.'
|
|
491
558
|
end
|
|
492
559
|
|
|
493
560
|
private
|
|
@@ -127,10 +127,15 @@ module Solargraph
|
|
|
127
127
|
block_call_type(api_map, name_pin, locals)
|
|
128
128
|
end
|
|
129
129
|
end
|
|
130
|
-
# @type new_signature_pin [Pin::Signature]
|
|
131
130
|
new_signature_pin = ol.resolve_generics_from_context_until_complete(ol.generics, atypes, nil, nil,
|
|
132
131
|
blocktype)
|
|
133
|
-
|
|
132
|
+
# @todo It shouldn't be necessary to choose either generics or macros
|
|
133
|
+
new_return_type = if new_signature_pin.return_type.defined?
|
|
134
|
+
new_signature_pin.return_type
|
|
135
|
+
else
|
|
136
|
+
named_types = p.parameter_names.zip(arguments.map { |arg| ComplexType.try_parse(simple_convert(arg.node).to_s) }).to_h
|
|
137
|
+
p.typify(api_map).expand(named_types)
|
|
138
|
+
end
|
|
134
139
|
self_type = if head?
|
|
135
140
|
# If we're at the head of the chain, we called a
|
|
136
141
|
# method somewhere that marked itself as returning
|
|
@@ -153,8 +158,7 @@ module Solargraph
|
|
|
153
158
|
# the docs were written - from the method pin.
|
|
154
159
|
# @todo Need to add nil check here
|
|
155
160
|
if new_return_type.defined?
|
|
156
|
-
type = with_params(new_return_type.self_to_type(self_type), self_type).qualify(api_map,
|
|
157
|
-
*p.gates)
|
|
161
|
+
type = with_params(new_return_type.self_to_type(self_type), self_type).qualify(api_map, *p.gates)
|
|
158
162
|
end
|
|
159
163
|
type ||= ComplexType::UNDEFINED
|
|
160
164
|
end
|
|
@@ -162,15 +166,6 @@ module Solargraph
|
|
|
162
166
|
end
|
|
163
167
|
p = p.with_single_signature(new_signature_pin) unless new_signature_pin.nil?
|
|
164
168
|
next p.proxy(type) if type.defined?
|
|
165
|
-
if !p.macros.empty?
|
|
166
|
-
result = process_macro(p, api_map, name_pin.context, locals)
|
|
167
|
-
# @sg-ignore flow sensitive typing should be able to handle redefinition
|
|
168
|
-
next result unless result.return_type.undefined?
|
|
169
|
-
elsif !p.directives.empty?
|
|
170
|
-
result = process_directive(p, api_map, name_pin.context, locals)
|
|
171
|
-
# @sg-ignore flow sensitive typing should be able to handle redefinition
|
|
172
|
-
next result unless result.return_type.undefined?
|
|
173
|
-
end
|
|
174
169
|
p
|
|
175
170
|
end
|
|
176
171
|
logger.debug do
|
|
@@ -191,69 +186,6 @@ module Solargraph
|
|
|
191
186
|
end
|
|
192
187
|
end
|
|
193
188
|
|
|
194
|
-
# @param pin [Pin::Base]
|
|
195
|
-
# @param api_map [ApiMap]
|
|
196
|
-
# @param context [ComplexType, ComplexType::UniqueType]
|
|
197
|
-
# @param locals [::Array<Solargraph::Pin::LocalVariable, Solargraph::Pin::Parameter>]
|
|
198
|
-
# @return [Pin::Base]
|
|
199
|
-
def process_macro pin, api_map, context, locals
|
|
200
|
-
pin.macros.each do |macro|
|
|
201
|
-
# @todo 'Wrong argument type for
|
|
202
|
-
# Solargraph::Source::Chain::Call#inner_process_macro:
|
|
203
|
-
# macro expected YARD::Tags::MacroDirective, received
|
|
204
|
-
# generic<Elem>' is because we lose 'rooted' information
|
|
205
|
-
# in the 'Chain::Array' class internally, leaving
|
|
206
|
-
# ::Array#each shadowed when it shouldn't be.
|
|
207
|
-
result = inner_process_macro(pin, macro, api_map, context, locals)
|
|
208
|
-
return result unless result.return_type.undefined?
|
|
209
|
-
end
|
|
210
|
-
Pin::ProxyType.anonymous(ComplexType::UNDEFINED, source: :chain)
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
# @param pin [Pin::Method]
|
|
214
|
-
# @param api_map [ApiMap]
|
|
215
|
-
# @param context [ComplexType, ComplexType::UniqueType]
|
|
216
|
-
# @param locals [::Array<Solargraph::Pin::LocalVariable, Solargraph::Pin::Parameter>]
|
|
217
|
-
# @return [Pin::ProxyType]
|
|
218
|
-
def process_directive pin, api_map, context, locals
|
|
219
|
-
pin.directives.each do |dir|
|
|
220
|
-
macro = api_map.named_macro(dir.tag.name)
|
|
221
|
-
next if macro.nil?
|
|
222
|
-
result = inner_process_macro(pin, macro, api_map, context, locals)
|
|
223
|
-
return result unless result.return_type.undefined?
|
|
224
|
-
end
|
|
225
|
-
Pin::ProxyType.anonymous ComplexType::UNDEFINED, source: :chain
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
# @param pin [Pin::Base]
|
|
229
|
-
# @param macro [YARD::Tags::MacroDirective]
|
|
230
|
-
# @param api_map [ApiMap]
|
|
231
|
-
# @param context [ComplexType, ComplexType::UniqueType]
|
|
232
|
-
# @param locals [::Array<Pin::LocalVariable, Pin::Parameter>]
|
|
233
|
-
# @return [Pin::ProxyType]
|
|
234
|
-
def inner_process_macro pin, macro, api_map, context, locals
|
|
235
|
-
vals = arguments.map { |c| Pin::ProxyType.anonymous(c.infer(api_map, pin, locals), source: :chain) }
|
|
236
|
-
txt = macro.tag.text.clone
|
|
237
|
-
# @sg-ignore Need to add nil check here
|
|
238
|
-
if txt.empty? && macro.tag.name
|
|
239
|
-
named = api_map.named_macro(macro.tag.name)
|
|
240
|
-
txt = named.tag.text.clone if named
|
|
241
|
-
end
|
|
242
|
-
i = 1
|
|
243
|
-
vals.each do |v|
|
|
244
|
-
# @sg-ignore Need to add nil check here
|
|
245
|
-
txt.gsub!(/\$#{i}/, v.context.namespace)
|
|
246
|
-
i += 1
|
|
247
|
-
end
|
|
248
|
-
# @sg-ignore Need to add nil check here
|
|
249
|
-
docstring = Solargraph::Source.parse_docstring(txt).to_docstring
|
|
250
|
-
tag = docstring.tag(:return)
|
|
251
|
-
unless tag.nil? || tag.types.nil?
|
|
252
|
-
return Pin::ProxyType.anonymous(ComplexType.try_parse(*tag.types), source: :chain)
|
|
253
|
-
end
|
|
254
|
-
Pin::ProxyType.anonymous(ComplexType::UNDEFINED, source: :chain)
|
|
255
|
-
end
|
|
256
|
-
|
|
257
189
|
# @param docstring [YARD::Docstring]
|
|
258
190
|
# @param context [ComplexType]
|
|
259
191
|
# @return [ComplexType, nil]
|
|
@@ -61,13 +61,6 @@ module Solargraph
|
|
|
61
61
|
@pins ||= []
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
-
# @param position [Solargraph::Position]
|
|
65
|
-
# @return [Solargraph::Pin::Closure]
|
|
66
|
-
def closure_at position
|
|
67
|
-
# @sg-ignore Need to add nil check here
|
|
68
|
-
pins.select { |pin| pin.is_a?(Pin::Closure) and pin.location.range.contain?(position) }.last
|
|
69
|
-
end
|
|
70
|
-
|
|
71
64
|
# @param source_position [Position]
|
|
72
65
|
# @param comment_position [Position]
|
|
73
66
|
# @param comment [String]
|
|
@@ -108,135 +101,12 @@ module Solargraph
|
|
|
108
101
|
# @param directive [YARD::Tags::Directive]
|
|
109
102
|
# @return [void]
|
|
110
103
|
def process_directive source_position, comment_position, directive
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
location = Location.new(@filename, Range.new(comment_position, comment_position))
|
|
114
|
-
case directive.tag.tag_name
|
|
115
|
-
when 'method'
|
|
116
|
-
namespace = closure_at(source_position) || @pins.first
|
|
117
|
-
# @sg-ignore Need to add nil check here
|
|
118
|
-
namespace = closure_at(comment_position) if namespace.location.range.start.line < comment_position.line
|
|
119
|
-
begin
|
|
120
|
-
src = Solargraph::Source.load_string("def #{directive.tag.name};end", @source.filename)
|
|
121
|
-
region = Parser::Region.new(source: src, closure: namespace)
|
|
122
|
-
# @type [Array<Pin::Method>]
|
|
123
|
-
method_gen_pins = Parser.process_node(src.node, region).first.select { |pin| pin.is_a?(Pin::Method) }
|
|
124
|
-
gen_pin = method_gen_pins.last
|
|
125
|
-
return if gen_pin.nil?
|
|
126
|
-
# Move the location to the end of the line so it gets recognized
|
|
127
|
-
# as originating from a comment
|
|
128
|
-
shifted = Solargraph::Position.new(comment_position.line,
|
|
129
|
-
@code.lines[comment_position.line].to_s.chomp.length)
|
|
130
|
-
# @todo: Smelly instance variable access
|
|
131
|
-
gen_pin.instance_variable_set(:@comments, docstring.all.to_s)
|
|
132
|
-
gen_pin.instance_variable_set(:@location, Solargraph::Location.new(@filename, Range.new(shifted, shifted)))
|
|
133
|
-
gen_pin.instance_variable_set(:@explicit, false)
|
|
134
|
-
@pins.push gen_pin
|
|
135
|
-
rescue Parser::SyntaxError
|
|
136
|
-
# @todo Handle error in directive
|
|
137
|
-
end
|
|
138
|
-
when 'attribute'
|
|
139
|
-
return if directive.tag.name.nil?
|
|
140
|
-
namespace = closure_at(source_position)
|
|
141
|
-
t = directive.tag.types.nil? || directive.tag.types.empty? ? nil : directive.tag.types.join
|
|
142
|
-
if t.nil? || t.include?('r')
|
|
143
|
-
pins.push Solargraph::Pin::Method.new(
|
|
144
|
-
location: location,
|
|
145
|
-
closure: namespace,
|
|
146
|
-
name: directive.tag.name,
|
|
147
|
-
comments: docstring.all.to_s,
|
|
148
|
-
scope: namespace.is_a?(Pin::Singleton) ? :class : :instance,
|
|
149
|
-
visibility: :public,
|
|
150
|
-
explicit: false,
|
|
151
|
-
attribute: true,
|
|
152
|
-
source: :source_map
|
|
153
|
-
)
|
|
154
|
-
end
|
|
155
|
-
if t.nil? || t.include?('w')
|
|
156
|
-
method_pin = Solargraph::Pin::Method.new(
|
|
157
|
-
location: location,
|
|
158
|
-
closure: namespace,
|
|
159
|
-
name: "#{directive.tag.name}=",
|
|
160
|
-
comments: docstring.all.to_s,
|
|
161
|
-
scope: namespace.is_a?(Pin::Singleton) ? :class : :instance,
|
|
162
|
-
visibility: :public,
|
|
163
|
-
attribute: true,
|
|
164
|
-
source: :source_map
|
|
165
|
-
)
|
|
166
|
-
pins.push method_pin
|
|
167
|
-
method_pin.parameters.push Pin::Parameter.new(name: 'value', decl: :arg, closure: pins.last,
|
|
168
|
-
source: :source_map)
|
|
169
|
-
if pins.last.return_type.defined?
|
|
170
|
-
pins.last.docstring.add_tag YARD::Tags::Tag.new(:param, '', pins.last.return_type.to_s.split(', '),
|
|
171
|
-
'value')
|
|
172
|
-
end
|
|
173
|
-
end
|
|
174
|
-
when 'visibility'
|
|
104
|
+
directive_processor = YardMap::Directives.for(directive)
|
|
105
|
+
return unless directive_processor
|
|
175
106
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
name = directive.tag.name
|
|
180
|
-
closure = closure_at(source_position) || @pins.first
|
|
181
|
-
# @sg-ignore Need to add nil check here
|
|
182
|
-
closure = closure_at(comment_position) if closure.location.range.start.line < comment_position.line
|
|
183
|
-
if closure.is_a?(Pin::Method) && no_empty_lines?(comment_position.line, source_position.line)
|
|
184
|
-
# @todo Smelly instance variable access
|
|
185
|
-
closure.instance_variable_set(:@visibility, kind)
|
|
186
|
-
else
|
|
187
|
-
matches = pins.select do |pin|
|
|
188
|
-
pin.is_a?(Pin::Method) && pin.name == name && pin.namespace == namespace && pin.context.scope == namespace.is_a?(Pin::Singleton) ? :class : :instance
|
|
189
|
-
end
|
|
190
|
-
matches.each do |pin|
|
|
191
|
-
# @todo Smelly instance variable access
|
|
192
|
-
pin.instance_variable_set(:@visibility, kind)
|
|
193
|
-
end
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
when 'parse'
|
|
197
|
-
begin
|
|
198
|
-
ns = closure_at(source_position)
|
|
199
|
-
# @sg-ignore Need to add nil check here
|
|
200
|
-
src = Solargraph::Source.load_string(directive.tag.text, @source.filename)
|
|
201
|
-
region = Parser::Region.new(source: src, closure: ns)
|
|
202
|
-
# @todo These pins may need to be marked not explicit
|
|
203
|
-
index = @pins.length
|
|
204
|
-
loff = if @code.lines[comment_position.line].strip.end_with?('@!parse')
|
|
205
|
-
comment_position.line + 1
|
|
206
|
-
else
|
|
207
|
-
comment_position.line
|
|
208
|
-
end
|
|
209
|
-
locals = []
|
|
210
|
-
ivars = []
|
|
211
|
-
Parser.process_node(src.node, region, @pins, locals, ivars)
|
|
212
|
-
@pins.concat ivars
|
|
213
|
-
# @sg-ignore Need to add nil check here
|
|
214
|
-
@pins[index..].each do |p|
|
|
215
|
-
# @todo Smelly instance variable access
|
|
216
|
-
p.location.range.start.instance_variable_set(:@line, p.location.range.start.line + loff)
|
|
217
|
-
p.location.range.ending.instance_variable_set(:@line, p.location.range.ending.line + loff)
|
|
218
|
-
end
|
|
219
|
-
rescue Parser::SyntaxError
|
|
220
|
-
# @todo Handle parser errors in !parse directives
|
|
221
|
-
end
|
|
222
|
-
when 'domain'
|
|
223
|
-
namespace = closure_at(source_position) || Pin::ROOT_PIN
|
|
224
|
-
# @sg-ignore flow sensitive typing should be able to handle redefinition
|
|
225
|
-
namespace.domains.concat directive.tag.types unless directive.tag.types.nil?
|
|
226
|
-
when 'override'
|
|
227
|
-
pins.push Pin::Reference::Override.new(location, directive.tag.name, docstring.tags,
|
|
228
|
-
source: :source_map)
|
|
229
|
-
when 'macro'
|
|
230
|
-
# @todo Handle macros
|
|
231
|
-
end
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
# @param line1 [Integer]
|
|
235
|
-
# @param line2 [Integer]
|
|
236
|
-
# @sg-ignore Need to add nil check here
|
|
237
|
-
def no_empty_lines? line1, line2
|
|
238
|
-
# @sg-ignore Need to add nil check here
|
|
239
|
-
@code.lines[line1..line2].none? { |line| line.strip.empty? }
|
|
107
|
+
@pins += directive_processor.process_directive(
|
|
108
|
+
@source, @pins, source_position, comment_position, directive
|
|
109
|
+
)
|
|
240
110
|
end
|
|
241
111
|
|
|
242
112
|
# @param comment [String]
|
|
@@ -152,6 +152,20 @@ module Solargraph
|
|
|
152
152
|
locals.select { |pin| pin.visible_at?(closure, location) }
|
|
153
153
|
end
|
|
154
154
|
|
|
155
|
+
# @return [Array<Parser::AST::Node>]
|
|
156
|
+
def method_call_nodes
|
|
157
|
+
# @sg-ignore node expected Parser::AST::Node, received Parser::AST::Node, nil
|
|
158
|
+
@method_call_nodes ||= Solargraph::Parser::ParserGem::NodeMethods.call_nodes_from(source.node)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# @param macro_method_names [Array<String>]
|
|
162
|
+
# @return [Array<Parser::AST::Node>]
|
|
163
|
+
def macro_method_candidates macro_method_names
|
|
164
|
+
return @macro_method_candidates if @macro_method_names == macro_method_names
|
|
165
|
+
@macro_method_names = macro_method_names
|
|
166
|
+
@macro_method_candidates = method_call_nodes.select { |node| macro_method_names.include?(node.children[1].to_s) }
|
|
167
|
+
end
|
|
168
|
+
|
|
155
169
|
class << self
|
|
156
170
|
# @param filename [String]
|
|
157
171
|
# @return [SourceMap]
|
data/lib/solargraph/version.rb
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Solargraph
|
|
4
|
-
VERSION = ENV.fetch('SOLARGRAPH_FORCE_VERSION', '0.
|
|
4
|
+
VERSION = ENV.fetch('SOLARGRAPH_FORCE_VERSION', '0.60.0')
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# @generic T
|
|
8
|
+
class A
|
|
9
|
+
# @yieldparam param0 [generic<T>]
|
|
10
|
+
def foo
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class B < A # [Integer]
|
|
15
|
+
def bar
|
|
16
|
+
foo { |param0| }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
b = B.new
|
|
21
|
+
b.foo do |arg|
|
|
22
|
+
arg #=> Integer
|
|
5
23
|
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Solargraph
|
|
4
|
+
class YardMap
|
|
5
|
+
module Directives
|
|
6
|
+
module AttributeDirective
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# @param source [Solargraph::Source]
|
|
10
|
+
# @param pins [Array<Solargraph::Pin::Base>]
|
|
11
|
+
# @param source_position [Position]
|
|
12
|
+
# @param comment_position [Position]
|
|
13
|
+
# @param directive [YARD::Tags::Directive]
|
|
14
|
+
# @return [Array<Solargraph::Pin::Method>]
|
|
15
|
+
def process_directive source, pins, source_position, comment_position, directive
|
|
16
|
+
new_pins = []
|
|
17
|
+
location = Location.new(source.filename, Range.new(comment_position, comment_position))
|
|
18
|
+
docstring = Solargraph::Source.parse_docstring(directive.tag.text.to_s).to_docstring
|
|
19
|
+
return [] if directive.tag.name.nil?
|
|
20
|
+
namespace = closure_at(pins, source_position)
|
|
21
|
+
t = directive.tag.types.nil? || directive.tag.types.empty? ? nil : directive.tag.types.join
|
|
22
|
+
if t.nil? || t.include?('r')
|
|
23
|
+
new_pins.push Solargraph::Pin::Method.new(
|
|
24
|
+
location: location,
|
|
25
|
+
closure: namespace,
|
|
26
|
+
name: directive.tag.name,
|
|
27
|
+
comments: docstring.all.to_s,
|
|
28
|
+
scope: namespace.is_a?(Pin::Singleton) ? :class : :instance,
|
|
29
|
+
visibility: :public,
|
|
30
|
+
explicit: false,
|
|
31
|
+
attribute: true,
|
|
32
|
+
source: :yard_map
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
if t.nil? || t.include?('w')
|
|
36
|
+
write_pin = Solargraph::Pin::Method.new(
|
|
37
|
+
location: location,
|
|
38
|
+
closure: namespace,
|
|
39
|
+
name: "#{directive.tag.name}=",
|
|
40
|
+
comments: docstring.all.to_s,
|
|
41
|
+
scope: namespace.is_a?(Pin::Singleton) ? :class : :instance,
|
|
42
|
+
visibility: :public,
|
|
43
|
+
attribute: true,
|
|
44
|
+
source: :yard_map
|
|
45
|
+
)
|
|
46
|
+
new_pins.push(write_pin)
|
|
47
|
+
write_pin.parameters.push Pin::Parameter.new(name: 'value', decl: :arg, closure: write_pin, source: :yard_map)
|
|
48
|
+
if write_pin.return_type&.defined?
|
|
49
|
+
write_pin.docstring.add_tag YARD::Tags::Tag.new(:param, '', write_pin.return_type.to_s.split(', '), 'value')
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
new_pins.compact
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @param [Array<Pin::Base>] pins
|
|
57
|
+
# @param [Position] position
|
|
58
|
+
# @return [Pin::Closure]
|
|
59
|
+
def closure_at pins, position
|
|
60
|
+
pins.select { |pin| pin.is_a?(Pin::Closure) and pin.location&.range&.contain?(position) }.last
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Solargraph
|
|
4
|
+
class YardMap
|
|
5
|
+
module Directives
|
|
6
|
+
module DomainDirective
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# @param source [Solargraph::Source]
|
|
10
|
+
# @param pins [Array<Solargraph::Pin::Base>]
|
|
11
|
+
# @param source_position [Position]
|
|
12
|
+
# @param _comment_position [Position]
|
|
13
|
+
# @param directive [YARD::Tags::Directive]
|
|
14
|
+
# @return [Array<Solargraph::Pin::Method>]
|
|
15
|
+
def process_directive source, pins, source_position, _comment_position, directive
|
|
16
|
+
namespace = closure_at(pins, source_position) || Pin::ROOT_PIN
|
|
17
|
+
namespace.domains.concat directive.tag.types unless directive.tag.types.nil?
|
|
18
|
+
[]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @param [Array<Pin::Base>] pins
|
|
22
|
+
# @param [Position] position
|
|
23
|
+
# @return [Pin::Namespace]
|
|
24
|
+
def closure_at pins, position
|
|
25
|
+
pins.select { |pin| pin.is_a?(Pin::Namespace) and pin.location&.range&.contain?(position) }.last
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|