Class: Solargraph::Workspace::Gemspecs

Inherits:
Object
  • Object
show all
Includes:
Logging
Defined in:
lib/solargraph/workspace/gemspecs.rb

Overview

Manages determining which gemspecs are available in a workspace

Constant Summary

Constants included from Logging

Logging::DEFAULT_LOG_LEVEL, Logging::LOG_LEVELS

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Logging

log_level, logger

Constructor Details

#initialize(directory, preferences: []) ⇒ Gemspecs

Returns a new instance of Gemspecs.

Parameters:

  • directory (String, nil)

    If nil, assume no bundle is present

  • preferences (Array<Gem::Specification>) (defaults to: [])


16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/solargraph/workspace/gemspecs.rb', line 16

def initialize directory, preferences: []
  # @todo an issue with both external bundles and the potential
  #   preferences feature is that bundler gives you a 'clean'
  #   rubygems environment with only the specified versions
  #   installed.  Possible alternatives:
  #
  #   *) prompt the user to run solargraph outside of bundler
  #      and treat all bundles as external
  #   *) reinstall the needed gems dynamically each time
  #   *) manipulate the rubygems/bundler environment
  @directory = directory && File.absolute_path(directory)
  # @todo implement preferences as a config-exposed feature
  @preferences = preferences
end

Instance Attribute Details

#directoryObject (readonly)

Returns the value of attribute directory.



12
13
14
# File 'lib/solargraph/workspace/gemspecs.rb', line 12

def directory
  @directory
end

#preferencesObject (readonly)

Returns the value of attribute preferences.



12
13
14
# File 'lib/solargraph/workspace/gemspecs.rb', line 12

def preferences
  @preferences
end

Class Method Details

.gem_specification_cacheHash{Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification => Gem::Specification}

Returns:

  • (Hash{Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification => Gem::Specification})


159
160
161
# File 'lib/solargraph/workspace/gemspecs.rb', line 159

def self.gem_specification_cache
  @gem_specification_cache ||= {}
end

Instance Method Details

#all_gemspecs_from_bundleArray<Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification>

Returns all gemspecs directly depended on by this workspace’s bundle (does not include transitive dependencies).

Returns:

  • (Array<Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification>)


147
148
149
150
151
152
153
154
155
156
# File 'lib/solargraph/workspace/gemspecs.rb', line 147

def all_gemspecs_from_bundle
  return [] unless directory

  @all_gemspecs_from_bundle ||=
    if in_this_bundle?
      all_gemspecs_from_this_bundle
    else
      all_gemspecs_from_external_bundle
    end
end

#fetch_dependencies(gemspec, out: $stderr) ⇒ Array<Gem::Specification>

Parameters:

  • gemspec (Gem::Specification)
  • out (IO, nil) (defaults to: $stderr)

    output stream for logging

Returns:

  • (Array<Gem::Specification>)


119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/solargraph/workspace/gemspecs.rb', line 119

def fetch_dependencies gemspec, out: $stderr
  all_gemspecs_from_bundle

  # @type [Hash{String => Gem::Specification}]
  deps_so_far = {}

  # @param runtime_dep [Gem::Dependency]
  # @param deps [Hash{String => Gem::Specification}]
  gem_dep_gemspecs = only_runtime_dependencies(gemspec).each_with_object(deps_so_far) do |runtime_dep, deps|
    dep = find_gem(runtime_dep.name, runtime_dep.requirement)
    next unless dep

    fetch_dependencies(dep, out: out).each { |sub_dep| deps[sub_dep.name] ||= sub_dep }

    deps[dep.name] ||= dep
  end

  # RBS tracks implicit dependencies, like how the YAML standard
  # library implies pulling in the psych library.
  stdlib_deps = RbsMap::StdlibMap.stdlib_dependencies(gemspec.name, gemspec.version) || []
  stdlib_dep_gemspecs = stdlib_deps.map { |dep| find_gem(dep['name'], dep['version']) }.compact
  (gem_dep_gemspecs.values.compact + stdlib_dep_gemspecs).uniq(&:name)
end

#find_gem(name, version = nil, out: $stderr) ⇒ Gem::Specification?

Parameters:

  • name (String)
  • version (String, nil) (defaults to: nil)
  • out (IO, nil) (defaults to: $stderr)

    output stream for logging

Returns:

  • (Gem::Specification, nil)


102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/solargraph/workspace/gemspecs.rb', line 102

def find_gem name, version = nil, out: $stderr
  # @sg-ignore flow sensitive typing should be able to handle redefinition
  specish = all_gemspecs_from_bundle.find { |specish| specish.name == name && specish.version == version }
  return to_gem_specification specish if specish

  # @sg-ignore flow sensitive typing should be able to handle redefinition
  specish = all_gemspecs_from_bundle.find { |specish| specish.name == name }
  # @sg-ignore flow sensitive typing needs to create separate ranges for postfix if
  return to_gem_specification specish if specish

  resolve_gem_ignoring_local_bundle name, version, out: out
end

#resolve_require(require) ⇒ ::Array<Gem::Specification>?

Take the path given to a ‘require’ statement in a source file and return the Gem::Specifications which will be brought into scope with it, so we can load pins for them.

Parameters:

  • require (String)

    The string sent to ‘require’ in the code to resolve, e.g. ‘rails’, ‘bundler/require’

Returns:

  • (::Array<Gem::Specification>, nil)


37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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
# File 'lib/solargraph/workspace/gemspecs.rb', line 37

def resolve_require require
  return nil if require.empty?

  # This is added in the parser when it sees 'Bundler.require' -
  # see https://bundler.io/guides/bundler_setup.html '
  #
  # @todo handle different arguments to Bundler.require
  return auto_required_gemspecs_from_bundler if require == 'bundler/require'

  # Determine gem name based on the require path
  file = "lib/#{require}.rb"
  spec_with_path = Gem::Specification.find_by_path(file)

  all_gemspecs = all_gemspecs_from_bundle

  gem_names_to_try = [
    spec_with_path&.name,
    require.tr('/', '-'),
    require.split('/').first
  ].compact.uniq
  # @param gem_name [String]
  gem_names_to_try.each do |gem_name|
    # @sg-ignore Unresolved call to == on Boolean
    gemspec = all_gemspecs.find { |gemspec| gemspec.name == gem_name }
    # @sg-ignore flow sensitive typing should be able to handle redefinition
    return [gemspec_or_preference(gemspec)] if gemspec

    begin
      gemspec = Gem::Specification.find_by_name(gem_name)
      # @sg-ignore flow sensitive typing should be able to handle redefinition
      return [gemspec_or_preference(gemspec)] if gemspec
    rescue Gem::MissingSpecError
      logger.debug do
        "Require path #{require} could not be resolved to a gem via find_by_path or guess of #{gem_name}"
      end
    end

    # look ourselves just in case this is hanging out somewhere
    # that find_by_path doesn't index
    gemspec = all_gemspecs.find do |spec|
      spec = to_gem_specification(spec) unless spec.respond_to?(:files)

      # @sg-ignore Translate to something flow sensitive typing understands
      spec&.files&.any? { |gemspec_file| file == gemspec_file }
    end
    # @sg-ignore flow sensitive typing should be able to handle redefinition
    return [gemspec_or_preference(gemspec)] if gemspec
  end

  nil
end

#stdlib_dependencies(stdlib_name) ⇒ Array<String>

Parameters:

  • stdlib_name (String)

Returns:

  • (Array<String>)


92
93
94
95
# File 'lib/solargraph/workspace/gemspecs.rb', line 92

def stdlib_dependencies stdlib_name
  deps = RbsMap::StdlibMap.stdlib_dependencies(stdlib_name, nil) || []
  deps.map { |dep| dep['name'] }.compact
end