Class: Solargraph::ApiMap

Inherits:
Object
  • Object
show all
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

Constant Summary collapse

@@core_map =
RbsMap::CoreMap.new

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(pins: []) ⇒ ApiMap

Returns a new instance of ApiMap.

Parameters:



25
26
27
28
29
30
# File 'lib/solargraph/api_map.rb', line 25

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

Instance Attribute Details

#missing_docsArray<String> (readonly)

Returns:

  • (Array<String>)


22
23
24
# File 'lib/solargraph/api_map.rb', line 22

def missing_docs
  @missing_docs
end

#unresolved_requiresArray<String> (readonly)

Returns:

  • (Array<String>)


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

def unresolved_requires
  @unresolved_requires
end

Class Method Details

.load(directory) ⇒ ApiMap

Create an ApiMap with a workspace in the specified directory.

Parameters:

  • directory (String)

Returns:



124
125
126
127
128
129
130
131
132
# File 'lib/solargraph/api_map.rb', line 124

def self.load directory
  api_map = new
  workspace = Solargraph::Workspace.new(directory)
  # api_map.catalog Bench.new(workspace: workspace)
  library = Library.new(workspace)
  library.map!
  api_map.catalog library.bench
  api_map
end

.load_with_cache(directory) ⇒ ApiMap

Create an ApiMap with a workspace in the specified directory and cache any missing gems.

Parameters:

  • directory (String)

Returns:



139
140
141
142
143
144
145
146
147
148
149
# File 'lib/solargraph/api_map.rb', line 139

def self.load_with_cache directory
  api_map = load(directory)
  return api_map if api_map.uncached_gemspecs.empty?

  api_map.uncached_gemspecs.each do |gemspec|
    Solargraph.logger.info "Caching #{gemspec.name} #{gemspec.version}..."
    pins = GemPins.build(gemspec)
    Solargraph::Cache.save('gems', "#{gemspec.name}-#{gemspec.version}.ser", pins)
  end
  load(directory)
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.

Parameters:

  • filename (String)

Returns:

  • (Boolean)


483
484
485
# File 'lib/solargraph/api_map.rb', line 483

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

#catalog(bench) ⇒ self

Catalog a bench.

Parameters:

Returns:

  • (self)


58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/solargraph/api_map.rb', line 58

def catalog bench
  implicit.clear
  @cache.clear
  @source_map_hash = bench.source_maps.map { |s| [s.filename, s] }.to_h
  pins = bench.source_maps.map(&:pins).flatten
  source_map_hash.each_value do |map|
    implicit.merge map.environ
  end
  unresolved_requires = (bench.external_requires + implicit.requires + bench.workspace.config.required).uniq
  @doc_map = DocMap.new(unresolved_requires, []) # @todo Implement gem preferences
  @store = Store.new(@@core_map.pins + @doc_map.pins + implicit.pins + pins)
  @unresolved_requires = @doc_map.unresolved_requires
  @missing_docs = [] # @todo Implement missing docs
  store.block_pins.each { |blk| blk.rebind(self) }
  self
end

#clip(cursor) ⇒ SourceMap::Clip

Parameters:

Returns:

Raises:



447
448
449
450
451
452
453
454
# File 'lib/solargraph/api_map.rb', line 447

def clip cursor
  raise FileNotFoundError, "ApiMap did not catalog #{cursor.filename}" unless source_map_hash.key?(cursor.filename)

  # @todo Clip caches are disabled pending resolution of a stale cache bug
  # cache.get_clip(cursor) ||
  #   SourceMap::Clip.new(self, cursor).tap { |clip| cache.set_clip(cursor, clip) }
  SourceMap::Clip.new(self, cursor)
end

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

Get a clip by filename and position.

Parameters:

  • filename (String)
  • position (Position, Array(Integer, Integer))

Returns:



115
116
117
118
# File 'lib/solargraph/api_map.rb', line 115

def clip_at filename, position
  position = Position.normalize(position)
  clip(cursor_at(filename, position))
end

#core_pinsArray<Pin::Base>

Returns:



81
82
83
# File 'lib/solargraph/api_map.rb', line 81

def core_pins
  @@core_map.pins
end

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

Parameters:

  • filename (String)
  • position (Position, Array(Integer, Integer))

Returns:

Raises:



104
105
106
107
108
# File 'lib/solargraph/api_map.rb', line 104

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

#document(path) ⇒ Enumerable<Pin::Base>

TODO:

This method is likely superfluous. Calling get_path_pins directly should be sufficient.

Get YARD documentation for the specified path.

Examples:

api_map.document('String#split')

Parameters:

  • path (String)

    The path to find

Returns:



422
423
424
# File 'lib/solargraph/api_map.rb', line 422

def document path
  get_path_pins(path)
end

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

Get an array of document symbols from a file.

Parameters:

  • filename (String)

Returns:



460
461
462
463
# File 'lib/solargraph/api_map.rb', line 460

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

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

Get an array of class variable pins for a namespace.

Parameters:

  • namespace (String)

    A fully qualified namespace

Returns:



277
278
279
# File 'lib/solargraph/api_map.rb', line 277

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

#get_complex_type_methods(complex_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)

Parameters:

  • complex_type (Solargraph::ComplexType)

    The complex type of the namespace

  • context (String) (defaults to: '')

    The context from which the type is referenced

  • internal (Boolean) (defaults to: false)

    True to include private methods

Returns:



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
# File 'lib/solargraph/api_map.rb', line 339

def get_complex_type_methods complex_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 complex_type.undefined? || complex_type.void?
  result = Set.new
  complex_type.each do |type|
    if type.duck_type?
      result.add Pin::DuckMethod.new(name: type.to_s[1..-1])
      result.merge 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.merge get_methods(type.tag, scope: type.scope, visibility: visibility)
      end
    end
  end
  result.to_a
end

#get_constants(namespace, *contexts) ⇒ Array<Solargraph::Pin::Base>

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

Parameters:

  • namespace (String)

    The namespace

  • contexts (Array<String>)

    The contexts

Returns:



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/solargraph/api_map.rb', line 185

def get_constants namespace, *contexts
  namespace ||= ''
  contexts.push '' if contexts.empty?
  cached = cache.get_constants(namespace, contexts)
  return cached.clone unless cached.nil?
  skip = Set.new
  result = []
  contexts.each do |context|
    fqns = qualify(namespace, context)
    visibility = [:public]
    visibility.push :private if fqns == context
    result.concat inner_get_constants(fqns, visibility, skip)
  end
  cache.set_constants(namespace, contexts, result)
  result
end

#get_global_variable_pinsEnumerable<Solargraph::Pin::GlobalVariable>

Returns:



287
288
289
# File 'lib/solargraph/api_map.rb', line 287

def get_global_variable_pins
  store.pins_by_class(Pin::GlobalVariable)
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.

Parameters:

  • namespace (String)

    A fully qualified namespace

  • scope (Symbol) (defaults to: :instance)

    :instance or :class

Returns:



260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/solargraph/api_map.rb', line 260

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(rooted_tag, name, scope: :instance) ⇒ Array<Solargraph::Pin::Method>

Get a stack of method pins for a method name in a potentially parameterized 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> ]

Parameters:

  • rooted_tag (String)

    Parameterized namespace, fully qualified

  • name (String)

    Method name to look up

  • scope (Symbol) (defaults to: :instance)

    :instance or :class

Returns:



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

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

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

Get an array of methods available in a particular context.

Parameters:

  • rooted_tag (String)

    The fully qualified namespace to search for methods

  • scope (Symbol) (defaults to: :instance)

    :class or :instance

  • visibility (Array<Symbol>) (defaults to: [:public])

    :public, :protected, and/or :private

  • deep (Boolean) (defaults to: true)

    True to include superclasses, mixins, etc.

Returns:



298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# File 'lib/solargraph/api_map.rb', line 298

def get_methods rooted_tag, scope: :instance, visibility: [:public], deep: true
  cached = cache.get_methods(rooted_tag, scope, visibility, deep)
  return cached.clone unless cached.nil?
  result = []
  skip = Set.new
  if rooted_tag == ''
    # @todo Implement domains
    implicit.domains.each do |domain|
      type = ComplexType.try_parse(domain)
      next if type.undefined?
      result.concat inner_get_methods(type.name, type.scope, visibility, deep, skip)
    end
    result.concat inner_get_methods(rooted_tag, :class, visibility, deep, skip)
    result.concat inner_get_methods(rooted_tag, :instance, visibility, deep, skip)
    result.concat inner_get_methods('Kernel', :instance, visibility, deep, skip)
  else
    result.concat inner_get_methods(rooted_tag, scope, visibility, deep, skip)
    result.concat inner_get_methods('Kernel', :instance, [:public], deep, skip) if visibility.include?(:private)
    result.concat inner_get_methods('Module', scope, visibility, deep, skip)
  end
  resolved = resolve_method_aliases(result, visibility)
  cache.set_methods(rooted_tag, scope, visibility, deep, resolved)
  resolved
end

#get_namespace_pins(namespace, context) ⇒ Array<Pin::Namespace>

Parameters:

  • namespace (String)
  • context (String)

Returns:



205
206
207
# File 'lib/solargraph/api_map.rb', line 205

def get_namespace_pins namespace, context
  store.fqns_pins(qualify(namespace, context))
end

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

Get an array of pins that match the specified path.

Parameters:

  • path (String)

Returns:



395
396
397
# File 'lib/solargraph/api_map.rb', line 395

def get_path_pins path
  get_path_suggestions(path)
end

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

Deprecated.

Use #get_path_pins instead.

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

Parameters:

  • path (String)

    The path to find

Returns:



386
387
388
389
# File 'lib/solargraph/api_map.rb', line 386

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

#get_symbolsEnumerable<Solargraph::Pin::Base>

Returns:



282
283
284
# File 'lib/solargraph/api_map.rb', line 282

def get_symbols
  store.get_symbols
end

#implicitEnviron

Returns:



97
98
99
# File 'lib/solargraph/api_map.rb', line 97

def implicit
  @implicit ||= Environ.new
end

#index(pins) ⇒ self

Parameters:

Returns:

  • (self)


34
35
36
37
38
39
40
41
42
# File 'lib/solargraph/api_map.rb', line 34

def index pins
  # @todo This implementation is incomplete. It should probably create a
  #   Bench.
  @source_map_hash = {}
  implicit.clear
  cache.clear
  @store = Store.new(@@core_map.pins + pins)
  self
end

#keyword_pinsEnumerable<Solargraph::Pin::Keyword>

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

Returns:



159
160
161
# File 'lib/solargraph/api_map.rb', line 159

def keyword_pins
  store.pins_by_class(Pin::Keyword)
end

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

Parameters:

Returns:



439
440
441
442
# File 'lib/solargraph/api_map.rb', line 439

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

#map(source) ⇒ self

Map a single source.

Parameters:

Returns:

  • (self)


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

def map source
  map = Solargraph::SourceMap.map(source)
  catalog Bench.new(source_maps: [map])
  self
end

#named_macro(name) ⇒ YARD::Tags::MacroDirective?

Parameters:

  • name (String)

Returns:

  • (YARD::Tags::MacroDirective, nil)


87
88
89
# File 'lib/solargraph/api_map.rb', line 87

def named_macro name
  store.named_macros[name]
end

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

True if the namespace exists.

Parameters:

  • name (String)

    The namespace to match

  • context (String) (defaults to: '')

    The context to search

Returns:

  • (Boolean)


175
176
177
# File 'lib/solargraph/api_map.rb', line 175

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

#namespacesSet<String>

An array of namespace names defined in the ApiMap.

Returns:

  • (Set<String>)


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

def namespaces
  store.namespaces
end

#pinsArray<Solargraph::Pin::Base>

Returns:



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

def pins
  store.pins
end

#qualify(tag, context_tag = '') ⇒ String?

Determine fully qualified tag for a given tag used inside the definition of another tag (“context”). This method will start the search in the specified context until it finds a match for the tag.

Does not recurse into qualifying the type parameters, but returns any which were passed in unchanged.

Parameters:

  • tag (String, nil)

    The namespace to match, complete with generic parameters set to appropriate values if available

  • context_tag (String) (defaults to: '')

    The context in which the tag was referenced; start from here to resolve the name

Returns:

  • (String, nil)

    fully qualified tag



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

def qualify tag, context_tag = ''
  return tag if ['self', nil].include?(tag)
  context_type = ComplexType.parse(context_tag)
  type = ComplexType.parse(tag)
  fqns = qualify_namespace(type.rooted_namespace, context_type.rooted_namespace)
  return nil if fqns.nil?
  fqns + type.substring
end

#qualify_namespace(namespace, context_namespace = '') ⇒ String?

Determine fully qualified namespace for a given namespace used inside the definition of another tag (“context”). This method will start the search in the specified context until it finds a match for the namespace.

Parameters:

  • namespace (String, nil)

    The namespace to match

  • context_namespace (String) (defaults to: '')

    The context namespace in which the tag was referenced; start from here to resolve the name

Returns:

  • (String, nil)

    fully qualified namespace



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

def qualify_namespace(namespace, context_namespace = '')
  cached = cache.get_qualified_namespace(namespace, context_namespace)
  return cached.clone unless cached.nil?
  result = if namespace.start_with?('::')
             inner_qualify(namespace[2..-1], '', Set.new)
           else
             inner_qualify(namespace, context_namespace, Set.new)
           end
  cache.set_qualified_namespace(namespace, context_namespace, result)
  result
end

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

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

Parameters:

  • query (String)

Returns:



430
431
432
433
434
435
# File 'lib/solargraph/api_map.rb', line 430

def query_symbols query
  Pin::Search.new(
    source_map_hash.values.flat_map(&:document_symbols),
    query
  ).results
end

#requiredSet<String>

Returns:

  • (Set<String>)


92
93
94
# File 'lib/solargraph/api_map.rb', line 92

def required
  @required ||= Set.new
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`

Parameters:

  • query (String)

    The text to match

Returns:

  • (Array<String>)


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

def search query
  pins.map(&:path)
      .compact
      .select { |path| path.downcase.include?(query.downcase) }
end

#source_map(filename) ⇒ SourceMap

Get a source map by filename.

Parameters:

  • filename (String)

Returns:

Raises:



474
475
476
477
# File 'lib/solargraph/api_map.rb', line 474

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

#source_mapsArray<SourceMap>

Returns:



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

def source_maps
  source_map_hash.values
end

#super_and_sub?(sup, sub) ⇒ Boolean

Check if a class is a superclass of another class.

Parameters:

  • sup (String)

    The superclass

  • sub (String)

    The subclass

Returns:

  • (Boolean)


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

def super_and_sub?(sup, sub)
  fqsup = qualify(sup)
  cls = qualify(sub)
  tested = []
  until fqsup.nil? || cls.nil? || tested.include?(cls)
    return true if cls == fqsup
    tested.push cls
    cls = qualify_superclass(cls)
  end
  false
end

#type_include?(host_ns, module_ns) ⇒ Boolean

Check if the host class includes the specified module, ignoring type parameters used.

Parameters:

  • host_ns (String)

    The class namesapce (no type parameters)

  • module_ns (String)

    The module namespace (no type parameters)

Returns:

  • (Boolean)


511
512
513
# File 'lib/solargraph/api_map.rb', line 511

def type_include?(host_ns, module_ns)
  store.get_includes(host_ns).map { |inc_tag| ComplexType.parse(inc_tag).name }.include?(module_ns)
end

#uncached_gemspecs::Array<Gem::Specification>

Returns:

  • (::Array<Gem::Specification>)


76
77
78
# File 'lib/solargraph/api_map.rb', line 76

def uncached_gemspecs
  @doc_map&.uncached_gemspecs || []
end