Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ Layout/ElseAlignment:
# Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, DefLikeMacros, AllowAdjacentOneLineDefs, NumberOfEmptyLines.
Layout/EmptyLineBetweenDefs:
Exclude:
- 'lib/solargraph/doc_map.rb'
- 'lib/solargraph/language_server/message/initialize.rb'
- 'lib/solargraph/pin/delegated_method.rb'

Expand Down
18 changes: 6 additions & 12 deletions lib/solargraph/api_map.rb
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,10 @@ def catalog bench
[self.class, @source_map_hash, conventions_environ, @doc_map, @unresolved_requires]
end

# @return [DocMap, nil]
attr_reader :doc_map
# @return [DocMap]
def doc_map
@doc_map ||= DocMap.new([], [], Workspace.new('.'))
end

# @return [::Array<Gem::Specification>]
def uncached_gemspecs
Expand Down Expand Up @@ -189,15 +191,7 @@ def self.load directory
# @param out [IO, nil]
# @return [void]
def cache_all_for_doc_map! out
@doc_map.cache_doc_map_gems!(out)
end

# @param gemspec [Gem::Specification]
# @param rebuild [Boolean]
# @param out [IO, nil]
# @return [void]
def cache_gem(gemspec, rebuild: false, out: nil)
@doc_map.cache(gemspec, rebuild: rebuild, out: out)
doc_map.cache_doc_map_gems!(out)
end

class << self
Expand Down Expand Up @@ -676,7 +670,7 @@ def resolve_method_aliases pins, visibility = [:public, :private, :protected]

# @return [Workspace, nil]
def workspace
@doc_map&.workspace
doc_map.workspace
end

# @param fq_reference_tag [String] A fully qualified whose method should be pulled in
Expand Down
1 change: 0 additions & 1 deletion lib/solargraph/doc_map.rb
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,6 @@ def only_runtime_dependencies gemspec
gemspec.dependencies - gemspec.development_dependencies
end


def inspect
self.class.inspect
end
Expand Down
67 changes: 44 additions & 23 deletions lib/solargraph/shell.rb
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,8 @@ def clear
# @param gem [String]
# @param version [String, nil]
def cache gem, version = nil
api_map = Solargraph::ApiMap.load(Dir.pwd)
spec = Gem::Specification.find_by_name(gem, version)
api_map.cache_gem(spec, rebuild: options[:rebuild], out: $stdout)
gems(gem + (version ? "=#{version}" : ''))
# '
end

desc 'uncache GEM [...GEM]', "Delete specific cached gem documentation"
Expand All @@ -132,28 +131,60 @@ def uncache *gems
next
end

spec = Gem::Specification.find_by_name(gem)
spec = workspace.find_gem(gem)
workspace.uncache_gem(spec, out: $stdout)
end
end

desc 'gems [GEM[=VERSION]]', 'Cache documentation for installed gems'
desc 'gems [GEM[=VERSION]...] [STDLIB...] [core]', 'Cache documentation for
installed libraries'
long_desc %( This command will cache the
generated type documentation for the specified libraries. While
Solargraph will generate this on the fly when needed, it takes
time. This command will generate it in advance, which can be
useful for CI scenarios.

With no arguments, it will cache all libraries in the current
workspace. If a gem or standard library name is specified, it
will cache that library's type documentation.

An equals sign after a gem will allow a specific gem version
to be cached.

The 'core' argument can be used to cache the type
documentation for the core Ruby libraries.

If the library is already cached, it will be rebuilt if the
--rebuild option is set.

Cached documentation is stored in #{PinCache.base_dir}, which
can be stored between CI runs.
)
option :rebuild, type: :boolean, desc: 'Rebuild existing documentation', default: false
# @param names [Array<String>]
# @return [void]
def gems *names
api_map = ApiMap.load('.')
# print time with ms
workspace = Solargraph::Workspace.new('.')

if names.empty?
Gem::Specification.to_a.each { |spec| do_cache spec, api_map }
STDERR.puts "Documentation cached for all #{Gem::Specification.count} gems."
workspace.cache_all_for_workspace!($stdout, rebuild: options[:rebuild])
else
$stderr.puts("Caching these gems: #{names}")
names.each do |name|
spec = Gem::Specification.find_by_name(*name.split('='))
do_cache spec, api_map
rescue Gem::MissingSpecError
warn "Gem '#{name}' not found"
if name == 'core'
PinCache.cache_core(out: $stdout) if !PinCache.core? || options[:rebuild]
next
end

gemspec = workspace.find_gem(*name.split('='))
if gemspec.nil?
warn "Gem '#{name}' not found"
else
workspace.cache_gem(gemspec, rebuild: options[:rebuild], out: $stdout)
end
end
STDERR.puts "Documentation cached for #{names.count} gems."
$stderr.puts "Documentation cached for #{names.count} gems."
end
end

Expand Down Expand Up @@ -194,7 +225,6 @@ def typecheck *files
filecount += 1
probcount += problems.length
end
# "
}
puts "Typecheck finished in #{time.real} seconds."
puts "#{probcount} problem#{probcount != 1 ? 's' : ''} found#{files.length != 1 ? " in #{filecount} of #{files.length} files" : ''}."
Expand Down Expand Up @@ -261,14 +291,5 @@ def pin_description pin
desc += " (#{pin.location.filename} #{pin.location.range.start.line})" if pin.location
desc
end

# @param gemspec [Gem::Specification]
# @param api_map [ApiMap]
# @return [void]
def do_cache gemspec, api_map
# @todo if the rebuild: option is passed as a positional arg,
# typecheck doesn't complain on the below line
api_map.cache_gem(gemspec, rebuild: options.rebuild, out: $stdout)
end
end
end
35 changes: 33 additions & 2 deletions lib/solargraph/workspace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ class Workspace
attr_reader :gemnames
alias source_gems gemnames

# @param directory [String] TODO: Document and test '' and '*' semantics
# @todo Remove '' and '*' special cases
# @param directory [String]
# @param config [Config, nil]
# @param server [Hash]
def initialize directory = '', config = nil, server = {}
Expand Down Expand Up @@ -66,7 +67,7 @@ def stdlib_dependencies stdlib_name
def global_environ
# empty docmap, since the result needs to work in any possible
# context here
@global_environ ||= Convention.for_global(DocMap.new([], [], self))
@global_environ ||= Convention.for_global(DocMap.new([], [], self, out: nil))
end

# @param gemspec [Gem::Specification, Bundler::LazySpecification]
Expand Down Expand Up @@ -195,6 +196,36 @@ def find_gem name, version = nil
nil
end

# @todo make this actually work against bundle instead of pulling
# all installed gemspecs -
# https://github.com/apiology/solargraph/pull/10
# @return [Array<Gem::Specification>]
def all_gemspecs_from_bundle
Gem::Specification.to_a
end

# @param out [IO, nil] output stream for logging
# @param rebuild [Boolean] whether to rebuild the pins even if they are cached
# @return [void]
def cache_all_for_workspace! out, rebuild: false
PinCache.cache_core(out: out) unless PinCache.core?

gem_specs = all_gemspecs_from_bundle
# try any possible standard libraries, but be quiet about it
stdlib_specs = pin_cache.possible_stdlibs.map { |stdlib| find_gem(stdlib, out: nil) }.compact
specs = (gem_specs + stdlib_specs)
specs.each do |spec|
pin_cache.cache_gem(gemspec: spec, rebuild: rebuild, out: out) unless pin_cache.cached?(spec)
end
out&.puts "Documentation cached for all #{specs.length} gems."

# do this after so that we prefer stdlib requires from gems,
# which are likely to be newer and have more pins
pin_cache.cache_all_stdlibs(out: out)

out&.puts "Documentation cached for core, standard library and gems."
end

# Synchronize the workspace from the provided updater.
#
# @param updater [Source::Updater]
Expand Down
24 changes: 24 additions & 0 deletions spec/api_map_method_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,30 @@ class B
end
end

describe '#cache_all_for_doc_map!' do
it 'can cache gems without a bench' do
api_map = Solargraph::ApiMap.new
doc_map = instance_double(Solargraph::DocMap, cache_doc_map_gems!: true)
allow(Solargraph::DocMap).to receive(:new).and_return(doc_map)
api_map.cache_all_for_doc_map!($stderr)
expect(doc_map).to have_received(:cache_doc_map_gems!).with($stderr)
end
end

describe '#workspace' do
it 'can get a default workspace without a bench' do
api_map = Solargraph::ApiMap.new
expect(api_map.workspace).not_to be_nil
end
end

describe '#uncached_gemspecs' do
it 'can get uncached gemspecs workspace without a bench' do
api_map = Solargraph::ApiMap.new
expect(api_map.uncached_gemspecs).not_to be_nil
end
end

describe '#get_methods' do
it 'recognizes mixin references from context' do
source = Solargraph::Source.load_string(%(
Expand Down
37 changes: 32 additions & 5 deletions spec/shell_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
before do
File.open(File.join(temp_dir, 'Gemfile'), 'w') do |file|
file.puts "source 'https://rubygems.org'"
file.puts "gem 'solargraph', path: #{File.expand_path('..', __dir__)}"
file.puts "gem 'solargraph', path: '#{File.expand_path('..', __dir__)}'"
end
output, status = Open3.capture2e("bundle install", chdir: temp_dir)
raise "Failure installing bundle: #{output}" unless status.success?
end

# @type cmd [Array<String>]
# @return [String]
def bundle_exec(*cmd)
# run the command in the temporary directory with bundle exec
output, status = Open3.capture2e("bundle exec #{cmd.join(' ')}", chdir: temp_dir)
Expand Down Expand Up @@ -111,8 +113,6 @@ def bundle_exec(*cmd)
end

it 'caches core without erroring out' do
pending('https://github.com/castwide/solargraph/pull/1061')

capture_both do
shell.uncache('core')
end
Expand All @@ -128,6 +128,35 @@ def bundle_exec(*cmd)
expect(output).to include("Gem 'solargraph123' not found")
end
end

context 'with mocked Workspace' do
let(:workspace) { instance_double(Solargraph::Workspace) }
let(:gemspec) { instance_double(Gem::Specification, name: 'backport') }

before do
allow(Solargraph::Workspace).to receive(:new).and_return(workspace)
end

it 'caches all without erroring out' do
allow(workspace).to receive(:cache_all_for_workspace!)

_output = capture_both { shell.gems }

expect(workspace).to have_received(:cache_all_for_workspace!)
end

it 'caches single gem without erroring out' do
allow(workspace).to receive(:find_gem).with('backport').and_return(gemspec)
allow(workspace).to receive(:cache_gem)

capture_both do
shell.options = { rebuild: false }
shell.gems('backport')
end

expect(workspace).to have_received(:cache_gem).with(gemspec, out: an_instance_of(StringIO), rebuild: false)
end
end
end

describe 'cache' do
Expand All @@ -139,8 +168,6 @@ def bundle_exec(*cmd)
subject(:call) { shell.cache('nonexistentgem8675309') }

it 'gives a good error message' do
pending('https://github.com/castwide/solargraph/pull/1061')

# capture stderr output
expect { call }.to output(/not found/).to_stderr
end
Expand Down
37 changes: 37 additions & 0 deletions spec/workspace_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,41 @@
Solargraph::Workspace.new('./path', config)
}.not_to raise_error
end

describe '#cache_all_for_workspace!' do
let(:pin_cache) { instance_double(Solargraph::PinCache) }

before do
allow(Solargraph::PinCache).to receive(:cache_core)
allow(Solargraph::PinCache).to receive(:possible_stdlibs)
allow(Solargraph::PinCache).to receive(:new).and_return(pin_cache)
allow(pin_cache).to receive_messages(cache_gem: nil, possible_stdlibs: [])
allow(Solargraph::PinCache).to receive(:cache_all_stdlibs)
end

it 'caches core pins' do
allow(Solargraph::PinCache).to receive_messages(core?: false)
allow(pin_cache).to receive_messages(cached?: true,
cache_all_stdlibs: nil)

workspace.cache_all_for_workspace!(nil, rebuild: false)

expect(Solargraph::PinCache).to have_received(:cache_core).with(out: nil)
end

it 'caches gems' do
gemspec = instance_double(Gem::Specification, name: 'test_gem', version: '1.0.0')
allow(Gem::Specification).to receive(:to_a).and_return([gemspec])
allow(pin_cache).to receive(:cached?).and_return(false)
allow(pin_cache).to receive(:cache_all_stdlibs).with(out: nil)

allow(Solargraph::PinCache).to receive_messages(core?: true,
possible_stdlibs: [])

workspace.cache_all_for_workspace!(nil, rebuild: false)

expect(pin_cache).to have_received(:cache_gem).with(gemspec: gemspec, out: nil,
rebuild: false)
end
end
end