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

Returns a new instance of ApiMap

Parameters:


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)

Returns:

  • (Array<String>)

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

Returns:


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.

Parameters:

  • directory (String)

Returns:


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.

Parameters:

  • filename (String)

Returns:

  • (Boolean)

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.

Parameters:

Returns:

  • (self)

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

Parameters:

Returns:

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.

Parameters:

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

Returns:


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

Parameters:

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

Returns:


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

Parameters:

  • path (String)

    The path to find

Returns:

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

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.

Parameters:

  • filename (String)

Returns:


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.

Parameters:

  • namespace (String)

    A fully qualified namespace

Returns:


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)

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:


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.

Parameters:

  • namespace (String)

    The namespace

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

    The context

Returns:


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.

Parameters:

  • namespace (String)

    A fully qualified namespace

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

    :instance or :class

Returns:


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

Parameters:

  • fqns (String)
  • name (String)
  • scope (Symbol)

    :instance or :class

Returns:


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.

Parameters:

  • fqns (String)

    The fully qualified namespace to search for methods

  • scope (Symbol)

    :class or :instance

  • visibility (Array<Symbol>)

    :public, :protected, and/or :private

  • deep (Boolean)

    True to include superclasses, mixins, etc.

Returns:


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.

Parameters:

  • path (String)

Returns:


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.

Parameters:

  • path (String)

    The path to find

Returns:


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>

Returns:


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

def get_symbols
  store.get_symbols
end

#index(pins) ⇒ self

Parameters:

Returns:

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

Parameters:

Returns:


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.

Parameters:

Returns:

  • (self)

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.

Parameters:

  • name (String)

    The namespace to match

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

    The context to search

Returns:

  • (Boolean)

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.

Returns:

  • (Array<String>)

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

def namespaces
  store.namespaces
end

#pinsArray<Solargraph::Pin::Base>

Returns:


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.

Parameters:

  • namespace (String, nil)

    The namespace to match

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

    The context to search

Returns:

  • (String)

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.

Parameters:

  • query (String)

Returns:


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

Parameters:


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`

Parameters:

  • query (String)

    The text to match

Returns:

  • (Array<String>)

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.

Parameters:

  • filename (String)

Returns:

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>

Returns:


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.

Parameters:

  • sup (String)

    The superclass

  • sub (String)

    The subclass

Returns:

  • (Boolean)

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

Returns:

  • (Boolean)

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

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