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:



152
153
154
155
156
157
158
159
160
# File 'lib/solargraph/api_map.rb', line 152

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, out = IO::NULL) ⇒ ApiMap

TODO:

IO::NULL is incorrectly inferred to be a String.

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

@sg-ignore

Parameters:

  • directory (String)
  • out (IO) (defaults to: IO::NULL)

    The output stream for messages

Returns:



172
173
174
175
176
177
178
179
180
181
182
# File 'lib/solargraph/api_map.rb', line 172

def self.load_with_cache directory, out = IO::NULL
  api_map = load(directory)
  return api_map if api_map.uncached_gemspecs.empty?

  api_map.uncached_gemspecs.each do |gemspec|
    out.puts "Caching gem #{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

#==(other) ⇒ Object



43
44
45
# File 'lib/solargraph/api_map.rb', line 43

def ==(other)
  self.eql?(other)
end

#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)


538
539
540
# File 'lib/solargraph/api_map.rb', line 538

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

#catalog(bench) ⇒ self

Catalog a bench.

Parameters:

Returns:

  • (self)


77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/solargraph/api_map.rb', line 77

def catalog bench
  old_api_hash = @source_map_hash&.values&.map(&:api_hash)
  need_to_uncache = (old_api_hash != bench.source_maps.map(&:api_hash))
  @source_map_hash = bench.source_maps.map { |s| [s.filename, s] }.to_h
  pins = bench.source_maps.flat_map(&:pins).flatten
  implicit.clear
  source_map_hash.each_value do |map|
    implicit.merge map.environ
  end
  unresolved_requires = (bench.external_requires + implicit.requires + bench.workspace.config.required).to_a.compact.uniq
  if @unresolved_requires != unresolved_requires || @doc_map&.uncached_gemspecs&.any?
    @doc_map = DocMap.new(unresolved_requires, [], bench.workspace.rbs_collection_path) # @todo Implement gem preferences
    @unresolved_requires = unresolved_requires
    need_to_uncache = true
  end
  @store = Store.new(@@core_map.pins + @doc_map.pins + implicit.pins + pins)
  @cache.clear if need_to_uncache

  @missing_docs = [] # @todo Implement missing docs
  self
end

#clip(cursor) ⇒ SourceMap::Clip

Parameters:

Returns:

Raises:



505
506
507
508
509
# File 'lib/solargraph/api_map.rb', line 505

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

  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:



143
144
145
146
# File 'lib/solargraph/api_map.rb', line 143

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

#core_pinsArray<Pin::Base>

Returns:



109
110
111
# File 'lib/solargraph/api_map.rb', line 109

def core_pins
  @@core_map.pins
end

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

Parameters:

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

Returns:

Raises:



132
133
134
135
136
# File 'lib/solargraph/api_map.rb', line 132

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:



480
481
482
# File 'lib/solargraph/api_map.rb', line 480

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:



515
516
517
518
# File 'lib/solargraph/api_map.rb', line 515

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

#eql?(other) ⇒ Boolean

This is a mutable object, which is cached in the Chain class - if you add any fields which change the results of calls (not just caches), please also change ‘equality_fields` below.

Returns:

  • (Boolean)


38
39
40
41
# File 'lib/solargraph/api_map.rb', line 38

def eql?(other)
  self.class == other.class &&
    equality_fields == other.equality_fields
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:



315
316
317
# File 'lib/solargraph/api_map.rb', line 315

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:



397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
# File 'lib/solargraph/api_map.rb', line 397

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:



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

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:



325
326
327
# File 'lib/solargraph/api_map.rb', line 325

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:



298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/solargraph/api_map.rb', line 298

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:



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

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:



336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# File 'lib/solargraph/api_map.rb', line 336

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)
    unless %w[Class Class<Class>].include?(rooted_tag)
      result.map! do |pin|
        next pin unless pin.path == 'Class#new'
        init_pin = get_method_stack(rooted_tag, 'initialize').first
        next pin unless init_pin

        type = ComplexType.try_parse(ComplexType.try_parse(rooted_tag).namespace)
        Pin::Method.new(
          name: 'new',
          scope: :class,
          location: init_pin.location,
          parameters: init_pin.parameters,
          signatures: init_pin.signatures.map { |sig| sig.proxy(type) },
          return_type: type,
          comments: init_pin.comments,
          closure: init_pin.closure
        # @todo Hack to force TypeChecker#internal_or_core?
        ).tap { |pin| pin.source = :rbs }
      end
    end
    result.concat inner_get_methods('Kernel', :instance, [:public], deep, skip) if visibility.include?(:private)
    result.concat inner_get_methods('Module', scope, visibility, deep, skip) if scope == :module
  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:



238
239
240
# File 'lib/solargraph/api_map.rb', line 238

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:



453
454
455
# File 'lib/solargraph/api_map.rb', line 453

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:



444
445
446
447
# File 'lib/solargraph/api_map.rb', line 444

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

#get_symbolsEnumerable<Solargraph::Pin::Base>

Returns:



320
321
322
# File 'lib/solargraph/api_map.rb', line 320

def get_symbols
  store.get_symbols
end

#hashObject



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

def hash
  equality_fields.hash
end

#implicitEnviron

Returns:



125
126
127
# File 'lib/solargraph/api_map.rb', line 125

def implicit
  @implicit ||= Environ.new
end

#index(pins) ⇒ self

Parameters:

Returns:

  • (self)


53
54
55
56
57
58
59
60
61
# File 'lib/solargraph/api_map.rb', line 53

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:



192
193
194
# File 'lib/solargraph/api_map.rb', line 192

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

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

Parameters:

Returns:



497
498
499
500
# File 'lib/solargraph/api_map.rb', line 497

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)


67
68
69
70
71
# File 'lib/solargraph/api_map.rb', line 67

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)


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

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)


208
209
210
# File 'lib/solargraph/api_map.rb', line 208

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

#namespacesSet<String>

An array of namespace names defined in the ApiMap.

Returns:

  • (Set<String>)


199
200
201
# File 'lib/solargraph/api_map.rb', line 199

def namespaces
  store.namespaces
end

#pinsArray<Solargraph::Pin::Base>

Returns:



185
186
187
# File 'lib/solargraph/api_map.rb', line 185

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



256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/solargraph/api_map.rb', line 256

def qualify tag, context_tag = ''
  return tag if ['self', nil].include?(tag)
  context_type = ComplexType.try_parse(context_tag)
  return unless context_type

  type = ComplexType.try_parse(tag)
  return unless type

  fqns = qualify_namespace(type.rooted_namespace, context_type.rooted_namespace)
  return unless fqns

  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



280
281
282
283
284
285
286
287
288
289
290
# File 'lib/solargraph/api_map.rb', line 280

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:



488
489
490
491
492
493
# File 'lib/solargraph/api_map.rb', line 488

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

#requiredSet<String>

Returns:

  • (Set<String>)


120
121
122
# File 'lib/solargraph/api_map.rb', line 120

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>)


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

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:



529
530
531
532
# File 'lib/solargraph/api_map.rb', line 529

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:



521
522
523
# File 'lib/solargraph/api_map.rb', line 521

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)


547
548
549
550
551
552
553
554
555
556
557
# File 'lib/solargraph/api_map.rb', line 547

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)


566
567
568
# File 'lib/solargraph/api_map.rb', line 566

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>)


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

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