Class: Solargraph::ApiMap

Inherits:
Object
  • Object
show all
Includes:
SourceToYard
Defined in:
lib/solargraph/api_map.rb,
lib/solargraph/api_map/cache.rb,
lib/solargraph/api_map/store.rb,
lib/solargraph/api_map/source_to_yard.rb

Overview

An aggregate provider for information about workspaces, sources, gems, and the Ruby core.

Defined Under Namespace

Modules: SourceToYard Classes: Cache, Store

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from SourceToYard

#code_object_at, #code_object_paths, #rake_yard

Constructor Details

#initialize(pins: []) ⇒ ApiMap


20
21
22
23
24
25
26
# File 'lib/solargraph/api_map.rb', line 20

def initialize pins: []
  @source_map_hash = {}
  @cache = Cache.new
  @mutex = Mutex.new
  @method_alias_stack = []
  index pins
end

Instance Attribute Details

#unresolved_requiresArray<String> (readonly)


17
18
19
# File 'lib/solargraph/api_map.rb', line 17

def unresolved_requires
  @unresolved_requires
end

Class Method Details

.keywordsArray<Solargraph::Pin::Keyword>

An array of pins based on Ruby keywords (`if`, `end`, etc.).


164
165
166
167
168
# File 'lib/solargraph/api_map.rb', line 164

def self.keywords
  @keywords ||= CoreFills::KEYWORDS.map{ |s|
    Pin::Keyword.new(s)
  }.freeze
end

.load(directory) ⇒ ApiMap

Create an ApiMap with a workspace in the specified directory.


149
150
151
152
153
154
# File 'lib/solargraph/api_map.rb', line 149

def self.load directory
  api_map = self.new
  workspace = Solargraph::Workspace.new(directory)
  api_map.catalog Bundle.new(workspace: workspace)
  api_map
end

Instance Method Details

#bundled?(filename) ⇒ Boolean

True if the specified file was included in a bundle, i.e., it's either included in a workspace or open in a library.


465
466
467
# File 'lib/solargraph/api_map.rb', line 465

def bundled? filename
  source_map_hash.keys.include?(filename)
end

#catalog(bundle) ⇒ self

Catalog a bundle.


58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/solargraph/api_map.rb', line 58

def catalog bundle
  new_map_hash = {}
  # Bundle always needs to be merged if it adds or removes sources
  merged = (bundle.sources.length == source_map_hash.values.length)
  bundle.sources.each do |source|
    if source_map_hash.key?(source.filename)
      if source_map_hash[source.filename].code == source.code && source_map_hash[source.filename].source.synchronized? && source.synchronized?
        new_map_hash[source.filename] = source_map_hash[source.filename]
      elsif !source.synchronized?
        new_map_hash[source.filename] = source_map_hash[source.filename]
        # @todo Smelly instance variable access
        new_map_hash[source.filename].instance_variable_set(:@source, source)
      else
        map = Solargraph::SourceMap.map(source)
        if source_map_hash[source.filename].try_merge!(map)
          new_map_hash[source.filename] = source_map_hash[source.filename]
        else
          new_map_hash[source.filename] = map
          merged = false
        end
      end
    else
      map = Solargraph::SourceMap.map(source)
      new_map_hash[source.filename] = map
      merged = false
    end
  end
  return self if merged
  pins = []
  reqs = []
  # @param map [SourceMap]
  new_map_hash.values.each do |map|
    pins.concat map.pins
    reqs.concat map.requires.map(&:name)
  end
  reqs.concat bundle.workspace.config.required
  local_path_hash.clear
  unless bundle.workspace.require_paths.empty?
    reqs.delete_if do |r|
      result = false
      bundle.workspace.require_paths.each do |l|
        pn = Pathname.new(bundle.workspace.directory).join(l, "#{r}.rb")
        if new_map_hash.keys.include?(pn.to_s)
          local_path_hash[r] = pn.to_s
          result = true
          break
        end
      end
      result
    end
  end
  yard_map.change(reqs)
  new_store = Store.new(pins + yard_map.pins)
  @mutex.synchronize {
    @cache.clear
    @source_map_hash = new_map_hash
    @store = new_store
    @unresolved_requires = yard_map.unresolved_requires
    workspace_filenames.clear
    workspace_filenames.concat bundle.workspace.filenames
  }
  self
end

#clip(cursor) ⇒ SourceMap::Clip

Raises:


433
434
435
436
# File 'lib/solargraph/api_map.rb', line 433

def clip cursor
  raise FileNotFoundError, "ApiMap did not catalog #{cursor.filename}" unless source_map_hash.has_key?(cursor.filename)
  SourceMap::Clip.new(self, cursor)
end

#clip_at(filename, position) ⇒ SourceMap::Clip

Get a clip by filename and position.


140
141
142
143
# File 'lib/solargraph/api_map.rb', line 140

def clip_at filename, position
  position = Position.normalize(position)
  SourceMap::Clip.new(self, cursor_at(filename, position))
end

#cursor_at(filename, position) ⇒ Source::Cursor


129
130
131
132
133
# File 'lib/solargraph/api_map.rb', line 129

def cursor_at filename, position
  position = Position.normalize(position)
  raise "File not found: #{filename}" unless source_map_hash.has_key?(filename)
  source_map_hash[filename].cursor_at(position)
end

#document(path) ⇒ Array<YARD::CodeObject::Base>

Get YARD documentation for the specified path.

Examples:

api_map.document('String#split')

404
405
406
407
408
409
# File 'lib/solargraph/api_map.rb', line 404

def document path
  rake_yard(store)
  docs = []
  docs.push code_object_at(path) unless code_object_at(path).nil?
  docs
end

#document_symbols(filename) ⇒ Array<Pin::Symbol>

Get an array of document symbols from a file.


442
443
444
445
# File 'lib/solargraph/api_map.rb', line 442

def document_symbols filename
  return [] unless source_map_hash.has_key?(filename) # @todo Raise error?
  source_map_hash[filename].document_symbols
end

#get_class_variable_pins(namespace) ⇒ Array<Solargraph::Pin::ClassVariable>

Get an array of class variable pins for a namespace.


257
258
259
# File 'lib/solargraph/api_map.rb', line 257

def get_class_variable_pins(namespace)
  prefer_non_nil_variables(store.get_class_variables(namespace))
end

#get_complex_type_methods(type, context = '', internal = false) ⇒ Array<Solargraph::Pin::Base>

Get an array of method pins for a complex type.

The type's namespace and the context should be fully qualified. If the context matches the namespace type or is a subclass of the type, protected methods are included in the results. If protected methods are included and internal is true, private methods are also included.

Examples:

api_map = Solargraph::ApiMap.new
type = Solargraph::ComplexType.parse('String')
api_map.get_complex_type_methods(type)

317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# File 'lib/solargraph/api_map.rb', line 317

def get_complex_type_methods type, context = '', internal = false
  # This method does not qualify the complex type's namespace because
  # it can cause conflicts between similar names, e.g., `Foo` vs.
  # `Other::Foo`. It still takes a context argument to determine whether
  # protected and private methods are visible.
  return [] if type.undefined? || type.void?
  result = []
  if type.duck_type?
    type.select(&:duck_type?).each do |t|
      result.push Pin::DuckMethod.new(name: t.tag[1..-1])
    end
    result.concat get_methods('Object')
  else
    unless type.nil? || type.name == 'void'
      visibility = [:public]
      if type.namespace == context || super_and_sub?(type.namespace, context)
        visibility.push :protected
        visibility.push :private if internal
      end
      result.concat get_methods(type.namespace, scope: type.scope, visibility: visibility)
    end
  end
  result
end

#get_constants(namespace, context = '') ⇒ Array<Solargraph::Pin::Base>

Get suggestions for constants in the specified namespace. The result may contain both constant and namespace pins.


192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/solargraph/api_map.rb', line 192

def get_constants namespace, context = ''
  namespace ||= ''
  cached = cache.get_constants(namespace, context)
  return cached.clone unless cached.nil?
  skip = []
  result = []
  bases = context.split('::')
  while bases.length > 0
    built = bases.join('::')
    fqns = qualify(namespace, built)
    visibility = [:public]
    visibility.push :private if fqns == context
    result.concat inner_get_constants(fqns, visibility, skip)
    bases.pop
  end
  fqns = qualify(namespace, '')
  visibility = [:public]
  visibility.push :private if fqns == context
  result.concat inner_get_constants(fqns, visibility, skip)
  cache.set_constants(namespace, context, result)
  result
end

#get_global_variable_pinsArray<Solargraph::Pin::GlobalVariable>


267
268
269
270
# File 'lib/solargraph/api_map.rb', line 267

def get_global_variable_pins
  # @todo Slow version
  pins.select{|p| p.kind == Pin::GLOBAL_VARIABLE}
end

#get_instance_variable_pins(namespace, scope = :instance) ⇒ Array<Solargraph::Pin::InstanceVariable>

Get an array of instance variable pins defined in specified namespace and scope.


240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/solargraph/api_map.rb', line 240

def get_instance_variable_pins(namespace, scope = :instance)
  result = []
  used = [namespace]
  result.concat store.get_instance_variables(namespace, scope)
  sc = qualify_lower(store.get_superclass(namespace), namespace)
  until sc.nil? || used.include?(sc)
    used.push sc
    result.concat store.get_instance_variables(sc, scope)
    sc = qualify_lower(store.get_superclass(sc), sc)
  end
  result
end

#get_method_stack(fqns, name, scope: :instance) ⇒ Array<Solargraph::Pin::BaseMethod>

Get a stack of method pins for a method name in a namespace. The order of the pins corresponds to the ancestry chain, with highest precedence first.

Examples:

api_map.get_method_stack('Subclass', 'method_name')
  #=> [ <Subclass#method_name pin>, <Superclass#method_name pin> ]

354
355
356
# File 'lib/solargraph/api_map.rb', line 354

def get_method_stack fqns, name, scope: :instance
  get_methods(fqns, scope: scope, visibility: [:private, :protected, :public]).select{|p| p.name == name}
end

#get_methods(fqns, scope: :instance, visibility: [:public], deep: true) ⇒ Array<Solargraph::Pin::BaseMethod>

Get an array of methods available in a particular context.


279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/solargraph/api_map.rb', line 279

def get_methods fqns, scope: :instance, visibility: [:public], deep: true
  cached = cache.get_methods(fqns, scope, visibility, deep)
  return cached.clone unless cached.nil?
  result = []
  skip = []
  if fqns == ''
    # @todo Implement domains
    # domains.each do |domain|
    #   type = ComplexType.parse(domain).first
    #   result.concat inner_get_methods(type.name, type.scope, [:public], deep, skip)
    # end
    result.concat inner_get_methods(fqns, :class, visibility, deep, skip)
    result.concat inner_get_methods(fqns, :instance, visibility, deep, skip)
    result.concat inner_get_methods('Kernel', :instance, visibility, deep, skip)
  else
    result.concat inner_get_methods(fqns, scope, visibility, deep, skip)
  end
  resolved = resolve_method_aliases(result, visibility)
  cache.set_methods(fqns, scope, visibility, deep, resolved)
  resolved
end

#get_path_pins(path) ⇒ Array<Pin::Base>

Get an array of pins that match the specified path.


375
376
377
# File 'lib/solargraph/api_map.rb', line 375

def get_path_pins path
  get_path_suggestions(path)
end

#get_path_suggestions(path) ⇒ Array<Solargraph::Pin::Base>

Deprecated.

Use #get_path_pins instead.

Get an array of all suggestions that match the specified path.


364
365
366
367
368
369
# File 'lib/solargraph/api_map.rb', line 364

def get_path_suggestions path
  return [] if path.nil?
  result = []
  result.concat store.get_path_pins(path)
  resolve_method_aliases(result)
end

#get_symbolsArray<Solargraph::Pin::Base>


262
263
264
# File 'lib/solargraph/api_map.rb', line 262

def get_symbols
  store.get_symbols
end

#index(pins) ⇒ self


30
31
32
33
34
35
36
37
38
39
# File 'lib/solargraph/api_map.rb', line 30

def index pins
  @mutex.synchronize {
    @source_map_hash.clear
    @cache.clear
    @store = Store.new(pins + YardMap.new.pins)
    @unresolved_requires = []
    workspace_filenames.clear
  }
  self
end

#local_path_hashObject


122
123
124
# File 'lib/solargraph/api_map.rb', line 122

def local_path_hash
  @local_paths ||= {}
end

#locate_pins(location) ⇒ Array<Solargraph::Pin::Base>


425
426
427
428
# File 'lib/solargraph/api_map.rb', line 425

def locate_pins location
  return [] if location.nil? || !source_map_hash.has_key?(location.filename)
  source_map_hash[location.filename].locate_pins(location)
end

#map(source) ⇒ self

Map a single source.


45
46
47
48
# File 'lib/solargraph/api_map.rb', line 45

def map source
  catalog Bundle.new(opened: [source])
  self
end

#named_macro(name) ⇒ Object


50
51
52
# File 'lib/solargraph/api_map.rb', line 50

def named_macro name
  store.named_macros[name]
end

#namespace_exists?(name, context = '') ⇒ Boolean

True if the namespace exists.


182
183
184
# File 'lib/solargraph/api_map.rb', line 182

def namespace_exists? name, context = ''
  !qualify(name, context).nil?
end

#namespacesArray<String>

An array of namespace names defined in the ApiMap.


173
174
175
# File 'lib/solargraph/api_map.rb', line 173

def namespaces
  store.namespaces
end

#pinsArray<Solargraph::Pin::Base>


157
158
159
# File 'lib/solargraph/api_map.rb', line 157

def pins
  store.pins
end

#qualify(namespace, context = '') ⇒ String

Get a fully qualified namespace name. This method will start the search in the specified context until it finds a match for the name.


221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/solargraph/api_map.rb', line 221

def qualify namespace, context = ''
  return namespace if ['self', nil].include?(namespace)
  cached = cache.get_qualified_namespace(namespace, context)
  return cached.clone unless cached.nil?
  result = if namespace.start_with?('::')
             inner_qualify(namespace[2..-1], '', [])
           else
             inner_qualify(namespace, context, [])
           end
  cache.set_qualified_namespace(namespace, context, result)
  result
end

#query_symbols(query) ⇒ Array<Pin::Base>

Get an array of all symbols in the workspace that match the query.


415
416
417
418
419
420
421
# File 'lib/solargraph/api_map.rb', line 415

def query_symbols query
  result = []
  source_map_hash.values.each do |s|
    result.concat s.query_symbols(query)
  end
  result
end

#require_reference_at(location) ⇒ Object


474
475
476
477
478
479
480
481
482
483
484
# File 'lib/solargraph/api_map.rb', line 474

def require_reference_at location
  map = source_map(location.filename)
  pin = map.requires.select { |pin| pin.location.range.contain?(location.range.start) }.first
  return nil if pin.nil?
  if local_path_hash.key?(pin.name)
    return Location.new(local_path_hash[pin.name], Solargraph::Range.from_to(0, 0, 0, 0))
  end
  yard_map.require_reference(pin.name)
rescue FileNotFoundError
  nil
end

#search(query) ⇒ Array<String>

Get a list of documented paths that match the query.

Examples:

api_map.query('str') # Results will include `String` and `Struct`

386
387
388
389
390
391
392
393
394
395
# File 'lib/solargraph/api_map.rb', line 386

def search query
  rake_yard(store)
  found = []
  code_object_paths.each do |k|
    if found.empty? || (query.include?('.') || query.include?('#')) || !(k.include?('.') || k.include?('#'))
      found.push k if k.downcase.include?(query.downcase)
    end
  end
  found
end

#source_map(filename) ⇒ SourceMap

Get a source map by filename.

Raises:


456
457
458
459
# File 'lib/solargraph/api_map.rb', line 456

def source_map filename
  raise FileNotFoundError, "Source map for `#{filename}` not found" unless source_map_hash.has_key?(filename)
  source_map_hash[filename]
end

#source_mapsArray<SourceMap>


448
449
450
# File 'lib/solargraph/api_map.rb', line 448

def source_maps
  source_map_hash.values
end

#super_and_sub?(sup, sub) ⇒ Boolean

Check if a class is a superclass of another class.


491
492
493
494
495
496
497
498
499
# File 'lib/solargraph/api_map.rb', line 491

def super_and_sub?(sup, sub)
  fqsup = qualify(sup)
  cls = qualify(store.get_superclass(sub), sub)
  until cls.nil?
    return true if cls == fqsup
    cls = qualify(store.get_superclass(cls), cls)
  end
  false
end

#workspaced?(filename) ⇒ Boolean


469
470
471
# File 'lib/solargraph/api_map.rb', line 469

def workspaced? filename
  workspace_filenames.include?(filename)
end