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,
lib/solargraph/api_map/bundler_methods.rb

Overview

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

Defined Under Namespace

Modules: BundlerMethods, SourceToYard Classes: Cache, Store

Constant Summary collapse

@@core_map =
RbsMap::CoreMap.new

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

Returns a new instance of ApiMap.

Parameters:



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

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


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

def missing_docs
  @missing_docs
end

#unresolved_requiresArray<String> (readonly)

Returns:

  • (Array<String>)


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

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:



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

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

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)


451
452
453
# File 'lib/solargraph/api_map.rb', line 451

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

#catalog(bench) ⇒ Object

Catalog a bench.

Parameters:



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/solargraph/api_map.rb', line 62

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
  external_requires = bench.external_requires
  source_map_hash.each_value do |map|
    implicit.merge map.environ
  end
  external_requires.merge implicit.requires
  external_requires.merge bench.workspace.config.required
  @rbs_maps = external_requires.map { |r| load_rbs_map(r) }
  unresolved_requires = @rbs_maps.reject(&:resolved?).map(&:library)
  yard_map.change(unresolved_requires, bench.workspace.directory, bench.workspace.source_gems)
  @store = Store.new(@@core_map.pins + @rbs_maps.flat_map(&:pins) + yard_map.pins + implicit.pins + pins)
  @unresolved_requires = yard_map.unresolved_requires
  @missing_docs = yard_map.missing_docs
  @rebindable_method_names = nil
  store.block_pins.each { |blk| blk.rebind(self) }
  self
end

#clip(cursor) ⇒ SourceMap::Clip

Parameters:

Returns:

Raises:



419
420
421
422
# File 'lib/solargraph/api_map.rb', line 419

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:



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

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

#core_pinsObject



84
85
86
# File 'lib/solargraph/api_map.rb', line 84

def core_pins
  @@core_map.pins
end

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

Parameters:

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

Returns:

Raises:



110
111
112
113
114
# File 'lib/solargraph/api_map.rb', line 110

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) ⇒ Array<YARD::CodeObjects::Base>

Get YARD documentation for the specified path.

Examples:

api_map.document('String#split')

Parameters:

  • path (String)

    The path to find

Returns:

  • (Array<YARD::CodeObjects::Base>)


391
392
393
394
395
396
# File 'lib/solargraph/api_map.rb', line 391

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.

Parameters:

  • filename (String)

Returns:



428
429
430
431
# File 'lib/solargraph/api_map.rb', line 428

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) ⇒ Array<Solargraph::Pin::ClassVariable>

Get an array of class variable pins for a namespace.

Parameters:

  • namespace (String)

    A fully qualified namespace

Returns:



244
245
246
# File 'lib/solargraph/api_map.rb', line 244

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:



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/solargraph/api_map.rb', line 305

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.namespace, 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_pinsArray<Solargraph::Pin::GlobalVariable>



254
255
256
# File 'lib/solargraph/api_map.rb', line 254

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:



227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/solargraph/api_map.rb', line 227

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::Method>

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

Parameters:

  • fqns (String)
  • name (String)
  • scope (Symbol) (defaults to: :instance)

    :instance or :class

Returns:



342
343
344
# File 'lib/solargraph/api_map.rb', line 342

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::Method>

Get an array of methods available in a particular context.

Parameters:

  • fqns (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:



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/solargraph/api_map.rb', line 265

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 = Set.new
  if fqns == ''
    # @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(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)
    result.concat inner_get_methods('Kernel', :instance, [:public], deep, skip) if visibility.include?(:private)
  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.

Parameters:

  • path (String)

Returns:



361
362
363
# File 'lib/solargraph/api_map.rb', line 361

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.

Parameters:

  • path (String)

    The path to find

Returns:



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

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

#get_symbolsArray<Solargraph::Pin::Base>

Returns:



249
250
251
# File 'lib/solargraph/api_map.rb', line 249

def get_symbols
  store.get_symbols
end

#implicitEnviron

Returns:



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

def implicit
  @implicit ||= Environ.new
end

#index(pins) ⇒ self

Parameters:

Returns:

  • (self)


39
40
41
42
43
44
45
46
47
# File 'lib/solargraph/api_map.rb', line 39

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:



411
412
413
414
# File 'lib/solargraph/api_map.rb', line 411

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)


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

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)


94
95
96
# File 'lib/solargraph/api_map.rb', line 94

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:



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

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.

Parameters:

  • namespace (String, nil)

    The namespace to match

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

    The context to search

Returns:

  • (String, nil)


208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/solargraph/api_map.rb', line 208

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], '', Set.new)
           else
             inner_qualify(namespace, context, Set.new)
           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.

Parameters:

  • query (String)

Returns:



402
403
404
405
406
407
# File 'lib/solargraph/api_map.rb', line 402

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

#rebindable_method_namesObject



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

def rebindable_method_names
  @rebindable_method_names ||= begin
    # result = yard_map.rebindable_method_names
    result = ['instance_eval', 'instance_exec', 'class_eval', 'class_exec', 'module_eval', 'module_exec', 'define_method'].to_set
    source_maps.each do |map|
      result.merge map.rebindable_method_names
    end
    result
  end
end

#requiredObject



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

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


372
373
374
375
376
377
378
379
380
381
382
# File 'lib/solargraph/api_map.rb', line 372

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

#source_map(filename) ⇒ SourceMap

Get a source map by filename.

Parameters:

  • filename (String)

Returns:

Raises:



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

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:



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

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)


460
461
462
463
464
465
466
467
468
469
470
# File 'lib/solargraph/api_map.rb', line 460

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, mod) ⇒ Boolean

Check if the host class includes the specified module.

Parameters:

  • host (String)

    The class

  • mod (String)

    The module

Returns:

  • (Boolean)


477
478
479
# File 'lib/solargraph/api_map.rb', line 477

def type_include?(host, mod)
  store.get_includes(host).include?(mod)
end

#yard_mapObject



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

def yard_map
  @yard_map ||= YardMap.new
end