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:



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

def initialize pins: []
  # @todo Extensions don't work yet
  # require_extensions
  @source_map_hash = {}
  @cache = Cache.new
  @mutex = Mutex.new
  index pins
end

Instance Attribute Details

#live_mapSolargraph::LiveMap (readonly)

Get a LiveMap associated with the current workspace.

Returns:



18
19
20
# File 'lib/solargraph/api_map.rb', line 18

def live_map
  @live_map
end

#unresolved_requiresArray<String> (readonly)

Returns:

  • (Array<String>)


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

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:



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

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:



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

def self.load directory
  # @todo How should this work?
  api_map = self.new #(Solargraph::Workspace.new(directory))
  workspace = Solargraph::Workspace.new(directory)
  api_map.catalog Bundle.new(workspace: workspace)
  api_map
end

Instance Method Details

#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
# 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.has_key?(source.filename)
      if source_map_hash[source.filename].code == source.code
        new_map_hash[source.filename] = source_map_hash[source.filename]
      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
  unless bundle.workspace.require_paths.empty?
    reqs.delete_if do |r|
      result = false
      bundle.workspace.require_paths.each do |l|
        if new_map_hash.keys.include?(File.join(l, "#{r}.rb"))
          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
  }
  resolve_method_aliases
  self
end

#clip(cursor) ⇒ SourceMap::Clip

Parameters:

Returns:

Raises:



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

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:

Returns:



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

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

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

Parameters:

Returns:



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

def cursor_at filename, 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>)


405
406
407
408
409
410
# File 'lib/solargraph/api_map.rb', line 405

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:



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

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:



251
252
253
# File 'lib/solargraph/api_map.rb', line 251

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 public methods for a complex type.

Parameters:

  • type (Solargraph::ComplexType)
  • context (String) (defaults to: '')
  • 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
329
330
331
# File 'lib/solargraph/api_map.rb', line 305

def get_complex_type_methods type, context = '', internal = false
  return [] if type.undefined? || type.void?
  result = []
  if type.duck_type?
    type.select(&:duck_type?).each do |t|
      result.push Pin::DuckMethod.new(nil, t.tag[1..-1])
    end
    result.concat get_methods('Object')
  else
    unless type.nil? || type.name == 'void'
      # @todo This method does not qualify the complex type's namespace
      #   because it can cause namespace conflicts, e.g., `Foo` vs.
      #   `Other::Foo`. It still takes a context argument because it
      #   uses context to determine whether protected and private methods
      #   are visible.
      # namespace = qualify(type.namespace, context)
      namespace = type.namespace
      visibility = [:public]
      if namespace == context || super_and_sub?(namespace, context)
        visibility.push :protected
        visibility.push :private if internal
      end
      result.concat get_methods(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:



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/solargraph/api_map.rb', line 184

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>



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

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:



236
237
238
239
240
241
242
243
244
245
# File 'lib/solargraph/api_map.rb', line 236

def get_instance_variable_pins(namespace, scope = :instance)
  result = []
  result.concat store.get_instance_variables(namespace, scope)
  sc = qualify(store.get_superclass(namespace), namespace)
  until sc.nil?
    result.concat store.get_instance_variables(sc, scope)
    sc = qualify(store.get_superclass(sc), sc)
  end
  result
end

#get_method_stack(fqns, name, scope: :instance) ⇒ Array<Solargraph::Pin::Base>

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:



345
346
347
348
349
350
351
352
353
# File 'lib/solargraph/api_map.rb', line 345

def get_method_stack fqns, name, scope: :instance
  # @todo This cache is still causing problems, but only when using
  #   Solargraph on Solargraph itself.
  # cached = cache.get_method_stack(fqns, name, scope)
  # return cached unless cached.nil?
  result = get_methods(fqns, scope: scope, visibility: [:private, :protected, :public]).select{|p| p.name == name}
  # cache.set_method_stack(fqns, name, scope, result)
  # result
end

#get_methods(fqns, scope: :instance, visibility: [:public], deep: true) ⇒ Array<Solargraph::Pin::Base>

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:



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/solargraph/api_map.rb', line 273

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
  # live = live_map.get_methods(fqns, '', scope.to_s, visibility.include?(:private))
  # unless live.empty?
  #   exist = result.map(&:name)
  #   result.concat live.reject{|p| exist.include?(p.name)}
  # end
  cache.set_methods(fqns, scope, visibility, deep, result)
  result
end

#get_path_pins(path) ⇒ Array<Pin::Base>

Get an array of pins that match the specified path.

Parameters:

  • path (String)

Returns:



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

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:



361
362
363
364
365
366
367
368
369
370
# File 'lib/solargraph/api_map.rb', line 361

def get_path_suggestions path
  return [] if path.nil?
  result = []
  result.concat store.get_path_pins(path)
  # if result.empty?
  #   lp = live_map.get_path_pin(path)
  #   result.push lp unless lp.nil?
  # end
  result
end

#get_symbolsArray<Solargraph::Pin::Base>

Returns:



256
257
258
# File 'lib/solargraph/api_map.rb', line 256

def get_symbols
  store.get_symbols
end

#index(pins) ⇒ self

Parameters:

Returns:

  • (self)


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

def index pins
  @mutex.synchronize {
    @source_map_hash.clear
    @cache.clear
    @store = Store.new(pins + YardMap.new.pins)
    @unresolved_requires = []
  }
  resolve_method_aliases
  self
end

#locate_pin(location) ⇒ Solargraph::Pin::Base

Parameters:

Returns:



426
427
428
429
# File 'lib/solargraph/api_map.rb', line 426

def locate_pin location
  return nil if location.nil? || !source_map_hash.has_key?(location.filename)
  source_map_hash[location.filename].locate_pin(location)
end

#map(source) ⇒ self

Map a single source.

Parameters:

Returns:

  • (self)


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

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

#named_macro(name) ⇒ Object



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)


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

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

#namespacesArray<String>

An array of namespace names defined in the ApiMap.

Returns:

  • (Array<String>)


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

def namespaces
  store.namespaces
end

#pinsArray<Solargraph::Pin::Base>

Returns:



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

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)


213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/solargraph/api_map.rb', line 213

def qualify namespace, context = ''
  # @todo The return for self might work better elsewhere
  return nil if namespace.nil?
  return qualify(context) if namespace == 'self'
  cached = cache.get_qualified_namespace(namespace, context)
  return cached.clone unless cached.nil?
  # result = inner_qualify(namespace, context, [])
  # result = result[2..-1] if !result.nil? && result.start_with?('::')
  if namespace.start_with?('::')
    result = inner_qualify(namespace[2..-1], '', [])
  else
    result = 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:



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

def query_symbols query
  result = []
  source_map_hash.values.each do |s|
    result.concat s.query_symbols(query)
  end
  result
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>)


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

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:



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

def source_map filename
  raise FileNotFoundError, "Source map for `#{filename}` not found" unless source_map_hash.has_key?(filename)
  source_map_hash[filename]
end