solargraph 0.53.3 → 0.54.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/lib/solargraph/api_map/cache.rb +2 -12
  4. data/lib/solargraph/api_map/store.rb +11 -6
  5. data/lib/solargraph/api_map.rb +80 -26
  6. data/lib/solargraph/complex_type/type_methods.rb +62 -30
  7. data/lib/solargraph/complex_type/unique_type.rb +117 -66
  8. data/lib/solargraph/complex_type.rb +41 -25
  9. data/lib/solargraph/doc_map.rb +19 -3
  10. data/lib/solargraph/gem_pins.rb +9 -1
  11. data/lib/solargraph/language_server/host/dispatch.rb +8 -1
  12. data/lib/solargraph/language_server/host/sources.rb +1 -61
  13. data/lib/solargraph/language_server/host.rb +39 -68
  14. data/lib/solargraph/language_server/message/base.rb +1 -1
  15. data/lib/solargraph/language_server/message/initialize.rb +14 -0
  16. data/lib/solargraph/language_server/message/text_document/formatting.rb +1 -0
  17. data/lib/solargraph/language_server/progress.rb +118 -0
  18. data/lib/solargraph/language_server.rb +1 -0
  19. data/lib/solargraph/library.rb +136 -96
  20. data/lib/solargraph/parser/node_processor/base.rb +3 -2
  21. data/lib/solargraph/parser/node_processor.rb +1 -0
  22. data/lib/solargraph/parser/parser_gem/class_methods.rb +3 -7
  23. data/lib/solargraph/parser/parser_gem/node_methods.rb +0 -4
  24. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +47 -0
  25. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +5 -3
  26. data/lib/solargraph/parser/parser_gem/node_processors.rb +2 -0
  27. data/lib/solargraph/pin/base_variable.rb +34 -5
  28. data/lib/solargraph/pin/block.rb +62 -7
  29. data/lib/solargraph/pin/delegated_method.rb +5 -1
  30. data/lib/solargraph/pin/documenting.rb +2 -0
  31. data/lib/solargraph/pin/method.rb +4 -2
  32. data/lib/solargraph/pin/parameter.rb +5 -28
  33. data/lib/solargraph/rbs_map/conversions.rb +12 -6
  34. data/lib/solargraph/rbs_map/core_fills.rb +4 -1
  35. data/lib/solargraph/rbs_map.rb +11 -3
  36. data/lib/solargraph/shell.rb +18 -13
  37. data/lib/solargraph/source/chain.rb +20 -0
  38. data/lib/solargraph/source/updater.rb +1 -0
  39. data/lib/solargraph/source.rb +0 -44
  40. data/lib/solargraph/source_map/clip.rb +1 -0
  41. data/lib/solargraph/source_map/mapper.rb +3 -2
  42. data/lib/solargraph/source_map.rb +10 -0
  43. data/lib/solargraph/type_checker.rb +44 -18
  44. data/lib/solargraph/version.rb +1 -1
  45. data/lib/solargraph/workspace/config.rb +2 -1
  46. data/lib/solargraph/workspace.rb +13 -0
  47. data/lib/solargraph/yard_map/mapper/to_method.rb +5 -2
  48. metadata +4 -3
  49. data/lib/solargraph/language_server/host/cataloger.rb +0 -57
@@ -9,10 +9,14 @@ module Solargraph
9
9
  # @return [Parser::AST::Node, nil]
10
10
  attr_reader :assignment
11
11
 
12
+ attr_accessor :mass_assignment
13
+
12
14
  # @param assignment [Parser::AST::Node, nil]
13
15
  def initialize assignment: nil, **splat
14
16
  super(**splat)
15
17
  @assignment = assignment
18
+ # @type [nil, ::Array(Parser::AST::Node, Integer)]
19
+ @mass_assignment = nil
16
20
  end
17
21
 
18
22
  def completion_item_kind
@@ -36,10 +40,12 @@ module Solargraph
36
40
  true
37
41
  end
38
42
 
39
- def probe api_map
40
- return ComplexType::UNDEFINED if @assignment.nil?
43
+ # @param parent_node [Parser::AST::Node]
44
+ # @param api_map [ApiMap]
45
+ # @return [::Array<ComplexType>]
46
+ def return_types_from_node(parent_node, api_map)
41
47
  types = []
42
- value_position_nodes_only(@assignment).each do |node|
48
+ value_position_nodes_only(parent_node).each do |node|
43
49
  # Nil nodes may not have a location
44
50
  if node.nil? || node.type == :NIL || node.type == :nil
45
51
  types.push ComplexType::NIL
@@ -55,8 +61,31 @@ module Solargraph
55
61
  types.push result unless result.undefined?
56
62
  end
57
63
  end
58
- return ComplexType::UNDEFINED if types.empty?
59
- ComplexType.try_parse(*types.map(&:to_s))
64
+ types
65
+ end
66
+
67
+ # @param api_map [ApiMap]
68
+ # @return [ComplexType]
69
+ def probe api_map
70
+ unless @assignment.nil?
71
+ types = return_types_from_node(@assignment, api_map)
72
+ return ComplexType.new(types.uniq) unless types.empty?
73
+ end
74
+
75
+ unless @mass_assignment.nil?
76
+ mass_node, index = @mass_assignment
77
+ types = return_types_from_node(mass_node, api_map)
78
+ types.map! do |type|
79
+ if type.tuple?
80
+ type.all_params[index]
81
+ elsif ['::Array', '::Set', '::Enumerable'].include?(type.rooted_name)
82
+ type.all_params.first
83
+ end
84
+ end.compact!
85
+ return ComplexType.new(types.uniq) unless types.empty?
86
+ end
87
+
88
+ ComplexType::UNDEFINED
60
89
  end
61
90
 
62
91
  def == other
@@ -18,17 +18,18 @@ module Solargraph
18
18
  @receiver = receiver
19
19
  @context = context
20
20
  @parameters = args
21
+ @return_type = ComplexType.parse('::Proc')
21
22
  @node = node
22
23
  end
23
24
 
24
25
  # @param api_map [ApiMap]
25
26
  # @return [void]
26
27
  def rebind api_map
27
- @binder ||= binder_or_nil(api_map)
28
+ @rebind ||= maybe_rebind(api_map)
28
29
  end
29
30
 
30
31
  def binder
31
- @binder || closure.binder
32
+ @rebind&.defined? ? @rebind : closure.binder
32
33
  end
33
34
 
34
35
  # @return [::Array<Parameter>]
@@ -41,20 +42,74 @@ module Solargraph
41
42
  @parameter_names ||= parameters.map(&:name)
42
43
  end
43
44
 
45
+ # @param yield_types [::Array<ComplexType>]
46
+ # @param parameters [::Array<Parameter>]
47
+ #
48
+ # @return [::Array<ComplexType>]
49
+ def destructure_yield_types(yield_types, parameters)
50
+ return yield_types if yield_types.length == parameters.length
51
+
52
+ # yielding a tuple into a block will destructure the tuple
53
+ if yield_types.length == 1
54
+ yield_type = yield_types.first
55
+ return yield_type.all_params if yield_type.tuple? && yield_type.all_params.length == parameters.length
56
+ end
57
+ parameters.map { ComplexType::UNDEFINED }
58
+ end
59
+
60
+ # @todo the next step with parameters, arguments, destructuring,
61
+ # kwargs, etc logic is probably either creating a Parameters
62
+ # or Callable pin that encapsulates and shares the logic
63
+ # between methods, blocks and signatures. It could live in
64
+ # Signature if Method didn't also own potentially different
65
+ # set of parameters, generics and return types.
66
+
67
+ # @param api_map [ApiMap]
68
+ # @return [::Array<ComplexType>]
69
+ def typify_parameters(api_map)
70
+ chain = Parser.chain(receiver, filename, node)
71
+ clip = api_map.clip_at(location.filename, location.range.start)
72
+ locals = clip.locals - [self]
73
+ meths = chain.define(api_map, closure, locals)
74
+ # @todo Convert logic to use signatures
75
+ meths.each do |meth|
76
+ next if meth.block.nil?
77
+
78
+ yield_types = meth.block.parameters.map(&:return_type)
79
+ # 'arguments' is what the method says it will yield to the
80
+ # block; 'parameters' is what the block accepts
81
+ argument_types = destructure_yield_types(yield_types, parameters)
82
+ param_types = argument_types.each_with_index.map do |arg_type, idx|
83
+ param = parameters[idx]
84
+ param_type = chain.base.infer(api_map, param, locals)
85
+ unless arg_type.nil?
86
+ if arg_type.generic? && param_type.defined?
87
+ namespace_pin = api_map.get_namespace_pins(meth.namespace, closure.namespace).first
88
+ arg_type.resolve_generics(namespace_pin, param_type)
89
+ else
90
+ arg_type.self_to(chain.base.infer(api_map, self, locals).namespace).qualify(api_map, meth.context.namespace)
91
+ end
92
+ end
93
+ end
94
+ return param_types if param_types.all?(&:defined?)
95
+ end
96
+ parameters.map { ComplexType::UNDEFINED }
97
+ end
98
+
44
99
  private
45
100
 
46
101
  # @param api_map [ApiMap]
47
- # @return [ComplexType, nil]
48
- def binder_or_nil api_map
49
- return nil unless receiver
102
+ # @return [ComplexType]
103
+ def maybe_rebind api_map
104
+ return ComplexType::UNDEFINED unless receiver
50
105
 
51
106
  chain = Parser.chain(receiver, location.filename)
52
107
  locals = api_map.source_map(location.filename).locals_at(location)
53
108
  receiver_pin = chain.define(api_map, self, locals).first
54
- return nil unless receiver_pin
109
+ return ComplexType::UNDEFINED unless receiver_pin
55
110
 
56
111
  types = receiver_pin.docstring.tag(:yieldreceiver)&.types
57
- return nil unless types&.any?
112
+ return ComplexType::UNDEFINED unless types&.any?
58
113
 
59
114
  target = chain.base.infer(api_map, receiver_pin, locals)
60
115
  target = full_context unless target.defined?
@@ -11,8 +11,9 @@ module Solargraph
11
11
  # given closure/scope, and the delegated method will then be resolved
12
12
  # to a method pin on that type.
13
13
  #
14
- # @param resolved_method [Method] an already resolved method pin.
14
+ # @param method [Method, nil] an already resolved method pin.
15
15
  # @param receiver [Source::Chain, nil] the source code used to resolve the receiver for this delegated method.
16
+ # @param name [String]
16
17
  # @param receiver_method_name [String] the method name that will be called on the receiver (defaults to :name).
17
18
  def initialize(method: nil, receiver: nil, name: method&.name, receiver_method_name: name, **splat)
18
19
  raise ArgumentError, 'either :method or :receiver is required' if (method && receiver) || (!method && !receiver)
@@ -37,6 +38,7 @@ module Solargraph
37
38
  end
38
39
  end
39
40
 
41
+ # @param api_map [ApiMap]
40
42
  def resolvable?(api_map)
41
43
  resolve_method(api_map)
42
44
  !!@resolved_method
@@ -79,6 +81,7 @@ module Solargraph
79
81
  # helper to print a source chain as code, probably not 100% correct.
80
82
  #
81
83
  # @param chain [Source::Chain]
84
+ # @return [String]
82
85
  def print_chain(chain)
83
86
  out = +''
84
87
  chain.links.each_with_index do |link, index|
@@ -91,6 +94,7 @@ module Solargraph
91
94
  end
92
95
  out << link.word
93
96
  end
97
+ out
94
98
  end
95
99
  end
96
100
  end
@@ -76,6 +76,8 @@ module Solargraph
76
76
  end
77
77
  end
78
78
 
79
+ # @param text [String]
80
+ # @return [String]
79
81
  def self.strip_html_comments text
80
82
  text.gsub(/<!--([\s\S]*?)-->/, '').strip
81
83
  end
@@ -135,7 +135,7 @@ module Solargraph
135
135
  result = []
136
136
  result.push generate_signature(parameters, top_type) if top_type.defined?
137
137
  result.concat(overloads.map { |meth| generate_signature(meth.parameters, meth.return_type) }) unless overloads.empty?
138
- result.push generate_signature(parameters, top_type) if result.empty?
138
+ result.push generate_signature(parameters, @return_type || ComplexType::UNDEFINED) if result.empty?
139
139
  result
140
140
  end
141
141
  end
@@ -288,7 +288,9 @@ module Solargraph
288
288
 
289
289
  # @return [::Array<Pin::Method>]
290
290
  def overloads
291
- @overloads ||= docstring.tags(:overload).map do |tag|
291
+ # Ignore overload tags with nil parameters. If it's not an array, the
292
+ # tag's source is likely malformed.
293
+ @overloads ||= docstring.tags(:overload).select(&:parameters).map do |tag|
292
294
  Pin::Signature.new(
293
295
  generics,
294
296
  tag.parameters.map do |src|
@@ -76,6 +76,7 @@ module Solargraph
76
76
  end
77
77
  end
78
78
 
79
+ # @return [ComplexType]
79
80
  def return_type
80
81
  if @return_type.nil?
81
82
  @return_type = ComplexType::UNDEFINED
@@ -99,7 +100,9 @@ module Solargraph
99
100
  #
100
101
  # @return [Integer]
101
102
  def index
102
- closure.parameter_names.index(name)
103
+ # @type [Method, Block]
104
+ method_pin = closure
105
+ method_pin.parameter_names.index(name)
103
106
  end
104
107
 
105
108
  # @param api_map [ApiMap]
@@ -140,33 +143,7 @@ module Solargraph
140
143
  # @return [ComplexType]
141
144
  def typify_block_param api_map
142
145
  if closure.is_a?(Pin::Block) && closure.receiver
143
- chain = Parser.chain(closure.receiver, filename, closure.node)
144
- clip = api_map.clip_at(location.filename, location.range.start)
145
- locals = clip.locals - [self]
146
- meths = chain.define(api_map, closure, locals)
147
- receiver_type = chain.base.infer(api_map, closure, locals)
148
- meths.each do |meth|
149
- block_signature = meth.block
150
- if block_signature
151
- yield_type = block_signature.parameters[index]&.return_type
152
- else
153
- # @todo move the yieldparam tag parsing logic into the
154
- # creation of Method pins so we don't need this else
155
- # statement
156
- yps = meth.docstring.tags(:yieldparam)
157
- unless yps[index].nil? or yps[index].types.nil? or yps[index].types.empty?
158
- yield_type = ComplexType.try_parse(yps[index].types.first)
159
- end
160
- end
161
- unless yield_type.nil?
162
- if yield_type.generic? && receiver_type.defined?
163
- namespace_pin = api_map.get_namespace_pins(meth.namespace, closure.namespace).first
164
- return yield_type.resolve_generics(namespace_pin, receiver_type)
165
- else
166
- return yield_type.self_to(chain.base.infer(api_map, closure, locals).namespace).qualify(api_map, meth.context.namespace)
167
- end
168
- end
169
- end
146
+ return closure.typify_parameters(api_map)[index]
170
147
  end
171
148
  ComplexType::UNDEFINED
172
149
  end
@@ -38,6 +38,8 @@ module Solargraph
38
38
  environment = RBS::Environment.from_loader(loader).resolve_type_names
39
39
  cursor = pins.length
40
40
  environment.declarations.each { |decl| convert_decl_to_pin(decl, Solargraph::Pin::ROOT_PIN) }
41
+ added_pins = pins[cursor..-1]
42
+ added_pins.each { |pin| pin.source = :rbs }
41
43
  end
42
44
 
43
45
  # @param decl [RBS::AST::Declarations::Base]
@@ -514,13 +516,17 @@ module Solargraph
514
516
 
515
517
  # @param type_name [RBS::TypeName]
516
518
  # @param type_args [Enumerable<RBS::Types::Bases::Base>]
517
- # @return [ComplexType]
519
+ # @return [ComplexType::UniqueType]
518
520
  def generic_type(type_name, type_args)
519
521
  base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s
520
- params = type_args.map { |a| other_type_to_tag(a) }.reject { |t| t == 'undefined' }
521
- params_str = params.empty? ? '' : "<#{params.join(', ')}>"
522
- type_string = "#{base}#{params_str}"
523
- ComplexType.parse(type_string)
522
+ params = type_args.map { |a| other_type_to_tag(a) }.reject { |t| t == 'undefined' }.map do |t|
523
+ ComplexType.try_parse(t)
524
+ end
525
+ if base == 'Hash' && params.length == 2
526
+ ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash)
527
+ else
528
+ ComplexType::UniqueType.new(base, [], params, rooted: true, parameters_type: :list)
529
+ end
524
530
  end
525
531
 
526
532
  # @param type_name [RBS::TypeName]
@@ -584,7 +590,7 @@ module Solargraph
584
590
  end
585
591
 
586
592
  # @param decl [RBS::AST::Declarations::Class, RBS::AST::Declarations::Module]
587
- # @param closure [Pin::Namespace]
593
+ # @param namespace [Pin::Namespace]
588
594
  # @return [void]
589
595
  def add_mixins decl, namespace
590
596
  decl.each_mixin do |mixin|
@@ -29,7 +29,10 @@ module Solargraph
29
29
  Override.from_comment('Module#class_eval', '@yieldreceiver [Class<self>]'),
30
30
  Override.from_comment('Module#class_exec', '@yieldreceiver [Class<self>]'),
31
31
  Override.from_comment('Module#module_eval', '@yieldreceiver [Module<self>]'),
32
- Override.from_comment('Module#module_exec', '@yieldreceiver [Module<self>]')
32
+ Override.from_comment('Module#module_exec', '@yieldreceiver [Module<self>]'),
33
+ # RBS does not define Class with a generic, so all calls to
34
+ # generic() return an 'untyped'. We can do better:
35
+ Override.method_return('Class#allocate', 'self')
33
36
  ]
34
37
 
35
38
  # HACK: Add Errno exception classes
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'pathname'
3
4
  require 'rbs'
4
5
 
5
6
  module Solargraph
@@ -17,10 +18,13 @@ module Solargraph
17
18
  attr_reader :library
18
19
 
19
20
  # @param library [String]
20
- def initialize library, version = nil
21
+ # @param version [String, nil]
22
+ # @param directories [Array<Pathname, String>]
23
+ def initialize library, version = nil, directories: []
21
24
  @library = library
22
25
  @version = version
23
26
  @collection = nil
27
+ @directories = directories
24
28
  loader = RBS::EnvironmentLoader.new(core_root: nil, repository: repository)
25
29
  add_library loader, library, version
26
30
  return unless resolved?
@@ -47,7 +51,11 @@ module Solargraph
47
51
  end
48
52
 
49
53
  def repository
50
- @repository ||= RBS::Repository.new(no_stdlib: false)
54
+ @repository ||= RBS::Repository.new(no_stdlib: false).tap do |repo|
55
+ # @todo Temporarily ignoring external directories due to issues with
56
+ # incomplete/broken gem_rbs_collection installations
57
+ # @directories.each { |dir| repo.add(Pathname.new(dir)) }
58
+ end
51
59
  end
52
60
 
53
61
  # @param library [String]
@@ -71,7 +79,7 @@ module Solargraph
71
79
  Solargraph.logger.info "#{short_name} successfully loaded library #{library}"
72
80
  true
73
81
  else
74
- Solargraph.logger.debug "#{short_name} failed to load library #{library}"
82
+ Solargraph.logger.info "#{short_name} failed to load library #{library}"
75
83
  false
76
84
  end
77
85
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'benchmark'
3
4
  require 'thor'
4
5
  require 'yard'
5
6
 
@@ -149,23 +150,28 @@ module Solargraph
149
150
  # @return [void]
150
151
  def typecheck *files
151
152
  directory = File.realpath(options[:directory])
152
- api_map = Solargraph::ApiMap.load_with_cache(directory)
153
+ api_map = Solargraph::ApiMap.load_with_cache(directory, $stdout)
154
+ probcount = 0
153
155
  if files.empty?
154
156
  files = api_map.source_maps.map(&:filename)
155
157
  else
156
158
  files.map! { |file| File.realpath(file) }
157
159
  end
158
- probcount = 0
159
160
  filecount = 0
160
- files.each do |file|
161
- checker = TypeChecker.new(file, api_map: api_map, level: options[:level].to_sym)
162
- problems = checker.problems
163
- next if problems.empty?
164
- problems.sort! { |a, b| a.location.range.start.line <=> b.location.range.start.line }
165
- puts problems.map { |prob| "#{prob.location.filename}:#{prob.location.range.start.line + 1} - #{prob.message}" }.join("\n")
166
- filecount += 1
167
- probcount += problems.length
168
- end
161
+
162
+ time = Benchmark.measure {
163
+ files.each do |file|
164
+ checker = TypeChecker.new(file, api_map: api_map, level: options[:level].to_sym)
165
+ problems = checker.problems
166
+ next if problems.empty?
167
+ problems.sort! { |a, b| a.location.range.start.line <=> b.location.range.start.line }
168
+ puts problems.map { |prob| "#{prob.location.filename}:#{prob.location.range.start.line + 1} - #{prob.message}" }.join("\n")
169
+ filecount += 1
170
+ probcount += problems.length
171
+ end
172
+ # "
173
+ }
174
+ puts "Typecheck finished in #{time.real} seconds."
169
175
  puts "#{probcount} problem#{probcount != 1 ? 's' : ''} found#{files.length != 1 ? " in #{filecount} of #{files.length} files" : ''}."
170
176
  # "
171
177
  exit 1 if probcount > 0
@@ -182,11 +188,10 @@ module Solargraph
182
188
  option :verbose, type: :boolean, aliases: :v, desc: 'Verbose output', default: false
183
189
  # @return [void]
184
190
  def scan
185
- require 'benchmark'
186
191
  directory = File.realpath(options[:directory])
187
192
  api_map = nil
188
193
  time = Benchmark.measure {
189
- api_map = Solargraph::ApiMap.load_with_cache(directory)
194
+ api_map = Solargraph::ApiMap.load_with_cache(directory, $stdout)
190
195
  api_map.pins.each do |pin|
191
196
  begin
192
197
  puts pin_description(pin) if options[:verbose]
@@ -29,6 +29,8 @@ module Solargraph
29
29
 
30
30
  @@inference_stack = []
31
31
  @@inference_depth = 0
32
+ @@inference_invalidation_key = nil
33
+ @@inference_cache = {}
32
34
 
33
35
  UNDEFINED_CALL = Chain::Call.new('<undefined>')
34
36
  UNDEFINED_CONSTANT = Chain::Constant.new('<undefined>')
@@ -81,7 +83,25 @@ module Solargraph
81
83
  # @param name_pin [Pin::Base]
82
84
  # @param locals [::Enumerable<Pin::LocalVariable>]
83
85
  # @return [ComplexType]
86
+ # @sg-ignore
84
87
  def infer api_map, name_pin, locals
88
+ out = nil
89
+ cached = @@inference_cache[[node, node.location, links.map(&:word), name_pin&.return_type, locals]] unless node.nil?
90
+ return cached if cached && @@inference_invalidation_key == api_map.hash
91
+ out = infer_uncached api_map, name_pin, locals
92
+ if @@inference_invalidation_key != api_map.hash
93
+ @@inference_cache = {}
94
+ @@inference_invalidation_key = api_map.hash
95
+ end
96
+ @@inference_cache[[node, node.location, links.map(&:word), name_pin&.return_type, locals]] = out unless node.nil?
97
+ out
98
+ end
99
+
100
+ # @param api_map [ApiMap]
101
+ # @param name_pin [Pin::Base]
102
+ # @param locals [::Enumerable<Pin::LocalVariable>]
103
+ # @return [ComplexType]
104
+ def infer_uncached api_map, name_pin, locals
85
105
  from_here = base.infer(api_map, name_pin, locals) unless links.length == 1
86
106
  if from_here
87
107
  name_pin = name_pin.proxy(from_here)
@@ -42,6 +42,7 @@ module Solargraph
42
42
  @output
43
43
  end
44
44
 
45
+ # @param text [String]
45
46
  # @return [String]
46
47
  def repair text
47
48
  changes.each do |ch|
@@ -92,50 +92,6 @@ module Solargraph
92
92
  stack
93
93
  end
94
94
 
95
- # Start synchronizing the source. This method updates the code without
96
- # parsing a new AST. The resulting Source object will be marked not
97
- # synchronized (#synchronized? == false).
98
- #
99
- # @param updater [Source::Updater]
100
- # @return [Source]
101
- def start_synchronize updater
102
- raise 'Invalid synchronization' unless updater.filename == filename
103
- real_code = updater.write(@code)
104
- src = Source.allocate
105
- src.filename = filename
106
- src.code = real_code
107
- src.version = updater.version
108
- src.parsed = parsed?
109
- src.repaired = updater.repair(@repaired)
110
- src.synchronized = false
111
- src.node = @node
112
- src.comments = @comments
113
- src.error_ranges = error_ranges
114
- src.last_updater = updater
115
- return src.finish_synchronize unless real_code.lines.length == @code.lines.length
116
- src
117
- end
118
-
119
- # Finish synchronizing a source that was updated via #start_synchronize.
120
- # This method returns self if the source is already synchronized. Otherwise
121
- # it parses the AST and returns a new synchronized Source.
122
- #
123
- # @return [Source]
124
- def finish_synchronize
125
- return self if synchronized?
126
- synced = Source.new(@code, filename)
127
- if synced.parsed?
128
- synced.version = version
129
- return synced
130
- end
131
- synced = Source.new(@repaired, filename)
132
- synced.error_ranges.concat (error_ranges + last_updater.changes.map(&:range))
133
- synced.code = @code
134
- synced.synchronized = true
135
- synced.version = version
136
- synced
137
- end
138
-
139
95
  # Synchronize the Source with an update. This method applies changes to the
140
96
  # code, parses the new code's AST, and returns the resulting Source object.
141
97
  #
@@ -11,6 +11,7 @@ module Solargraph
11
11
  def initialize api_map, cursor
12
12
  @api_map = api_map
13
13
  @cursor = cursor
14
+ block.rebind(api_map) if block.is_a?(Pin::Block)
14
15
  end
15
16
 
16
17
  # @return [Array<Pin::Base>] Relevant pins for infering the type of the Cursor's position
@@ -144,7 +144,7 @@ module Solargraph
144
144
  )
145
145
  end
146
146
  if t.nil? || t.include?('w')
147
- pins.push Solargraph::Pin::Method.new(
147
+ method_pin = Solargraph::Pin::Method.new(
148
148
  location: location,
149
149
  closure: namespace,
150
150
  name: "#{directive.tag.name}=",
@@ -153,7 +153,8 @@ module Solargraph
153
153
  visibility: :public,
154
154
  attribute: true
155
155
  )
156
- pins.last.parameters.push Pin::Parameter.new(name: 'value', decl: :arg, closure: pins.last)
156
+ pins.push method_pin
157
+ method_pin.parameters.push Pin::Parameter.new(name: 'value', decl: :arg, closure: pins.last)
157
158
  if pins.last.return_type.defined?
158
159
  pins.last.docstring.add_tag YARD::Tags::Tag.new(:param, '', pins.last.return_type.to_s.split(', '), 'value')
159
160
  end
@@ -40,6 +40,16 @@ module Solargraph
40
40
  @pin_select_cache[klass] ||= @pin_class_hash.select { |key, _| key <= klass }.values.flatten
41
41
  end
42
42
 
43
+ # A hash representing the state of the source map's API.
44
+ #
45
+ # ApiMap#catalog uses this value to determine whether it needs to clear its
46
+ # cache.
47
+ #
48
+ # @return [Integer]
49
+ def api_hash
50
+ @api_hash ||= (pins_by_class(Pin::Constant) + pins_by_class(Pin::Namespace).select { |pin| pin.namespace.to_s > '' } + pins_by_class(Pin::Reference) + pins_by_class(Pin::Method).map(&:node)).hash
51
+ end
52
+
43
53
  # @return [String]
44
54
  def filename
45
55
  source.filename