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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec.yml +4 -1
  3. data/.rubocop.yml +1 -0
  4. data/.rubocop_todo.yml +1 -1
  5. data/CHANGELOG.md +11 -0
  6. data/Gemfile +3 -0
  7. data/lib/solargraph/api_map/index.rb +13 -2
  8. data/lib/solargraph/api_map/store.rb +21 -6
  9. data/lib/solargraph/api_map.rb +34 -2
  10. data/lib/solargraph/complex_type/unique_type.rb +4 -0
  11. data/lib/solargraph/complex_type.rb +4 -0
  12. data/lib/solargraph/doc_map.rb +1 -0
  13. data/lib/solargraph/parser/parser_gem/node_methods.rb +42 -0
  14. data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +29 -5
  15. data/lib/solargraph/pin/base.rb +31 -3
  16. data/lib/solargraph/pin/callable.rb +2 -2
  17. data/lib/solargraph/pin/common.rb +12 -0
  18. data/lib/solargraph/pin/method.rb +56 -16
  19. data/lib/solargraph/rbs_map/conversions.rb +96 -145
  20. data/lib/solargraph/rbs_translator.rb +206 -0
  21. data/lib/solargraph/shell.rb +130 -63
  22. data/lib/solargraph/source/chain/call.rb +8 -76
  23. data/lib/solargraph/source_map/mapper.rb +5 -135
  24. data/lib/solargraph/source_map.rb +14 -0
  25. data/lib/solargraph/version.rb +19 -1
  26. data/lib/solargraph/yard_map/directives/attribute_directive.rb +65 -0
  27. data/lib/solargraph/yard_map/directives/domain_directive.rb +30 -0
  28. data/lib/solargraph/yard_map/directives/method_directive.rb +51 -0
  29. data/lib/solargraph/yard_map/directives/override_directive.rb +30 -0
  30. data/lib/solargraph/yard_map/directives/parse_directive.rb +53 -0
  31. data/lib/solargraph/yard_map/directives/visibility_directive.rb +70 -0
  32. data/lib/solargraph/yard_map/directives.rb +35 -0
  33. data/lib/solargraph/yard_map/macro.rb +113 -0
  34. data/lib/solargraph/yard_map/mapper.rb +19 -1
  35. data/lib/solargraph/yard_map.rb +2 -0
  36. data/lib/solargraph.rb +1 -0
  37. data/solargraph.gemspec +1 -0
  38. metadata +24 -1
@@ -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.rbs_collection_path, workspace.rbs_collection_config_path)
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
- # cache pins even if result is zero, so we don't retry building pins
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} - #{prob.message}"
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
- puts 'Parsing and mapping source files...'
424
- prepare_start = Time.now
425
- Vernier.profile(out: "#{options[:output_dir]}/parse_benchmark.json.gz", hooks: hooks) do
426
- puts 'Mapping libraries'
427
- host.prepare(directory)
428
- sleep 0.2 until host.libraries.all?(&:mapped?)
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
- puts 'Building the catalog...'
433
- catalog_start = Time.now
434
- Vernier.profile(out: "#{options[:output_dir]}/catalog_benchmark.json.gz", hooks: hooks) do
435
- host.catalog
436
- end
437
- catalog_time = Time.now - catalog_start
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
- # Determine test file
440
- if file
441
- test_file = File.join(directory, file)
442
- else
443
- test_file = File.join(directory, 'lib', 'other.rb')
444
- unless File.exist?(test_file)
445
- # Fallback to any Ruby file in the workspace
446
- workspace = Solargraph::Workspace.new(directory)
447
- test_file = workspace.filenames.find { |f| f.end_with?('.rb') }
448
- unless test_file
449
- warn 'No Ruby files found in workspace'
450
- return
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
- file_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(File.absolute_path(test_file))
479
+ file_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(File.absolute_path(test_file))
456
480
 
457
- puts "Profiling go-to-definition for #{test_file}"
458
- puts "Position: line #{options[:line]}, column #{options[:column]}"
481
+ puts "Profiling go-to-definition for #{test_file}"
482
+ puts "Position: line #{options[:line]}, column #{options[:column]}"
459
483
 
460
- definition_start = Time.now
461
- Vernier.profile(out: "#{options[:output_dir]}/definition_benchmark.json.gz", hooks: hooks) do
462
- message = Solargraph::LanguageServer::Message::TextDocument::Definition.new(
463
- host, {
464
- 'params' => {
465
- 'textDocument' => { 'uri' => file_uri },
466
- 'position' => { 'line' => options[:line], 'character' => options[:column] }
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
- puts 'Processing go-to-definition request...'
471
- result = message.process
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
- puts "Result: #{result.inspect}"
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
- definition_time = Time.now - definition_start
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
- new_return_type = new_signature_pin.return_type
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
- # @sg-ignore Need to add nil check here
112
- docstring = Solargraph::Source.parse_docstring(directive.tag.text).to_docstring
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
- kind = directive.tag.text&.to_sym
177
- return unless %i[private protected public].include?(kind)
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]
@@ -1,5 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Solargraph
4
- VERSION = ENV.fetch('SOLARGRAPH_FORCE_VERSION', '0.59.2')
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