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:



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

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:



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

def self.load directory
  api_map = new
  workspace = Solargraph::Workspace.new(directory)
  api_map.catalog Bench.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)


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

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

#catalog(bench) ⇒ self

Catalog a bench.

Parameters:

Returns:

  • (self)


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

def catalog bench
  new_map_hash = {}
  # Bench always needs to be merged if it adds or removes sources
  merged = (bench.sources.length == source_map_hash.values.length)
  bench.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 bench.pins.empty? && @store && merged
  implicit.clear
  pins = []
  reqs = Set.new
  # @param map [SourceMap]
  new_map_hash.each_value do |map|
    pins.concat map.pins
    reqs.merge map.requires.map(&:name)
  end
  pins.concat bench.pins
  reqs.merge bench.workspace.config.required
  @required = reqs
  bench.sources.each do |src|
    implicit.merge new_map_hash[src.filename].environ
  end
  # implicit.merge Convention.for_global(self)
  local_path_hash.clear
  unless bench.workspace.require_paths.empty?
    file_keys = new_map_hash.keys
    workspace_path = Pathname.new(bench.workspace.directory)
    reqs.delete_if do |r|
      bench.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
  br = reqs.include?('bundler/require') ? require_from_bundle(bench.workspace.directory) : {}
  reqs.merge br.keys
  yard_map.change(reqs.to_a, br, bench.workspace.gemnames)
  new_store = Store.new(yard_map.pins + implicit.pins + pins)
  @cache.clear
  @source_map_hash = new_map_hash
  @store = new_store
  @unresolved_requires = yard_map.unresolved_requires
  workspace_filenames.clear
  workspace_filenames.concat bench.workspace.filenames
  @rebindable_method_names = nil
  store.block_pins.each { |blk| blk.rebind(self) }
  self
end

#clip(cursor) ⇒ SourceMap::Clip

Parameters:

Returns:

Raises:



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

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:

Returns:



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

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:

Returns:

Raises:



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

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


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

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:



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

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:



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

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:



344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
# File 'lib/solargraph/api_map.rb', line 344

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:



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

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>



293
294
295
# File 'lib/solargraph/api_map.rb', line 293

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:



266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/solargraph/api_map.rb', line 266

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:



381
382
383
# File 'lib/solargraph/api_map.rb', line 381

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:



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

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:



400
401
402
# File 'lib/solargraph/api_map.rb', line 400

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:



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

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

#get_symbolsArray<Solargraph::Pin::Base>



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

def get_symbols
  store.get_symbols
end

#implicitEnviron

Returns:



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

def implicit
  @implicit ||= Environ.new
end

#index(pins) ⇒ self

Parameters:

Returns:

  • (self)


35
36
37
38
# File 'lib/solargraph/api_map.rb', line 35

def index pins
  catalog Bench.new(pins: pins)
  self
end

#keyword_pinsArray<Solargraph::Pin::Keyword>

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



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

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

#local_path_hashHash{String => String}

Returns:

  • (Hash{String => String})


146
147
148
# File 'lib/solargraph/api_map.rb', line 146

def local_path_hash
  @local_paths ||= {}
end

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

Parameters:

Returns:



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

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)


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

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

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

Parameters:

  • name (String)

Returns:

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


51
52
53
# File 'lib/solargraph/api_map.rb', line 51

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)


214
215
216
# File 'lib/solargraph/api_map.rb', line 214

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

#namespacesSet<String>

An array of namespace names defined in the ApiMap.

Returns:

  • (Set<String>)


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

def namespaces
  store.namespaces
end

#pinsArray<Solargraph::Pin::Base>



181
182
183
# File 'lib/solargraph/api_map.rb', line 181

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)


247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/solargraph/api_map.rb', line 247

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:



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

def query_symbols query
  result = []
  source_map_hash.each_value { |s| result.concat s.query_symbols(query) }
  result
end

#rebindable_method_namesObject



185
186
187
188
189
190
191
192
193
# File 'lib/solargraph/api_map.rb', line 185

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:



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

def require_reference_at location
  map = source_map(location.filename)
  pin = map.requires.select { |p| p.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

#requiredObject



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

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:



411
412
413
414
415
416
417
418
419
420
421
# File 'lib/solargraph/api_map.rb', line 411

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:



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

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:



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

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)


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

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)


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

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

#yard_mapYardMap

Returns:



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

def yard_map
  @yard_map ||= YardMap.new
end