Class: Solargraph::ApiMap

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

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from BundlerMethods

require_from_bundle, reset_require_from_bundle

Methods included from SourceToYard

#code_object_at, #code_object_paths, #rake_yard

Constructor Details

#initialize(pins: []) ⇒ ApiMap

Returns a new instance of ApiMap.

Parameters:



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

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

Instance Attribute Details

#unresolved_requiresArray<String> (readonly)

Returns:

  • (Array<String>)


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

def unresolved_requires
  @unresolved_requires
end

Class Method Details

.keywordsArray<Solargraph::Pin::Keyword>

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

Returns:



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

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.

Parameters:

  • directory (String)

Returns:



167
168
169
170
171
172
# File 'lib/solargraph/api_map.rb', line 167

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.

Parameters:

  • filename (String)

Returns:

  • (Boolean)


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

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

#catalog(bundle) ⇒ self

Catalog a bundle.

Parameters:

Returns:

  • (self)


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
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/solargraph/api_map.rb', line 63

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
  implicit.clear
  pins = []
  reqs = Set.new
  # @param map [SourceMap]
  new_map_hash.values.each do |map|
    implicit.merge map.environ
    pins.concat map.pins
    reqs.merge map.requires.map(&:name)
  end
  reqs.merge bundle.workspace.config.required
  local_path_hash.clear
  unless bundle.workspace.require_paths.empty?
    file_keys = new_map_hash.keys
    workspace_path = Pathname.new(bundle.workspace.directory)
    reqs.delete_if do |r|
      bundle.workspace.require_paths.any? do |base|
        pn = workspace_path.join(base, "#{r}.rb").to_s
        if file_keys.include? pn
          local_path_hash[r] = pn
          true
        else
          false
        end
      end
    end
  end
  reqs.merge implicit.requires
  pins.concat implicit.overrides
  br = reqs.include?('bundler/require') ? require_from_bundle(bundle.workspace.directory) : {}
  reqs.merge br.keys
  yard_map.change(reqs.to_a, br, bundle.workspace.gemnames)
  new_store = Store.new(pins + yard_map.pins)
  @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
  @rebindable_method_names = nil
  store.block_pins.each { |blk| blk.rebind(self) }
  self
end

#clip(cursor) ⇒ SourceMap::Clip

Parameters:

Returns:

Raises:



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

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.

Parameters:

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

Returns:



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

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

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

Parameters:

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

Returns:

Raises:



147
148
149
150
151
# File 'lib/solargraph/api_map.rb', line 147

def cursor_at filename, position
  position = Position.normalize(position)
  raise FileNotFoundError, "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')

Parameters:

  • path (String)

    The path to find

Returns:

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


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

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:



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

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.

Parameters:

  • namespace (String)

    A fully qualified namespace

Returns:



279
280
281
# File 'lib/solargraph/api_map.rb', line 279

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)

Parameters:

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



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

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, *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:



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

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>



289
290
291
# File 'lib/solargraph/api_map.rb', line 289

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:



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

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

Parameters:

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

    :instance or :class

Returns:



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

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.

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:



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

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, [: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)
    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:



398
399
400
# File 'lib/solargraph/api_map.rb', line 398

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:



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

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>

Returns:



284
285
286
# File 'lib/solargraph/api_map.rb', line 284

def get_symbols
  store.get_symbols
end

#implicitEnviron

Returns:



135
136
137
# File 'lib/solargraph/api_map.rb', line 135

def implicit
  @implicit ||= Environ.new
end

#index(pins) ⇒ self

Parameters:

Returns:

  • (self)


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

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

#local_path_hashHash{String => String}

Returns:

  • (Hash{String => String})


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

def local_path_hash
  @local_paths ||= {}
end

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

Parameters:

Returns:



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

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.

Parameters:

Returns:

  • (self)


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

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

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

Parameters:

  • name (String)

Returns:

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


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

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)


210
211
212
# File 'lib/solargraph/api_map.rb', line 210

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

#namespacesSet<String>

An array of namespace names defined in the ApiMap.

Returns:

  • (Set<String>)


201
202
203
# File 'lib/solargraph/api_map.rb', line 201

def namespaces
  store.namespaces
end

#pinsArray<Solargraph::Pin::Base>

Returns:



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

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)


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

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:



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

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

#rebindable_method_namesObject



179
180
181
182
183
184
185
186
187
# File 'lib/solargraph/api_map.rb', line 179

def rebindable_method_names
  @rebindable_method_names ||= begin
    result = yard_map.rebindable_method_names
    source_maps.each do |map|
      result.merge map.rebindable_method_names
    end
    result
  end
end

#require_reference_at(location) ⇒ Location

Parameters:

Returns:



501
502
503
504
505
506
507
508
509
510
511
# File 'lib/solargraph/api_map.rb', line 501

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`

Parameters:

  • query (String)

    The text to match

Returns:

  • (Array<String>)


409
410
411
412
413
414
415
416
417
418
# File 'lib/solargraph/api_map.rb', line 409

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.

Parameters:

  • filename (String)

Returns:

Raises:



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

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>

Returns:



471
472
473
# File 'lib/solargraph/api_map.rb', line 471

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)


518
519
520
521
522
523
524
525
526
# File 'lib/solargraph/api_map.rb', line 518

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

#workspaced?(filename) ⇒ Boolean

True if the specified file is included in the workspace.

Parameters:

  • filename (String)

Returns:

  • (Boolean)


495
496
497
# File 'lib/solargraph/api_map.rb', line 495

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