diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 218f598df..cfed714ca 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -196,7 +196,7 @@ jobs: # solargraph-rails supports Ruby 3.0+ # This job uses 3.2 due to a problem compiling sqlite3 in earlier versions ruby-version: '3.2' - bundler-cache: false + bundler-cache: true # https://github.com/apiology/solargraph/actions/runs/19400815835/job/55508092473?pr=17 rubygems: latest bundler: latest diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index f75bbd15d..be2dd710d 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -40,6 +40,15 @@ jobs: with: ruby-version: ${{ matrix.ruby-version }} bundler-cache: true + # Without this pin, ruby/setup-ruby uses whatever bundler ships + # with each matrix Ruby version individually, rather than one + # consistent version across the matrix. Confirmed by removing + # it: stdlib/default-gem resolution changed enough that + # Workspace::Gemspecs#resolve_require('yaml') started returning + # nil, failing spec/api_map_method_spec.rb's YAML test across + # most of the matrix. Keep this in sync with Gemfile.lock's + # BUNDLED WITH version, and with the same pin below. + bundler: 2.5.23 - name: Set rbs version run: echo "gem 'rbs', '${{ matrix.rbs-version }}'" >> .Gemfile # /home/runner/.rubies/ruby-head/lib/ruby/gems/3.5.0+2/gems/rbs-3.9.4/lib/rbs.rb:11: @@ -67,7 +76,15 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: '3.4' + # See the matching pin in the rspec matrix job above for why + # this is needed - keep both in sync with Gemfile.lock's + # BUNDLED WITH version. + bundler: 2.5.23 bundler-cache: true + - name: Install gems + run: | + bundle _2.5.23_ install + bundle update rbs # use latest available for this Ruby version - name: Update types run: bundle exec rbs collection update - name: Run tests diff --git a/.rubocop.yml b/.rubocop.yml index f4463bd11..e74b4decf 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -92,6 +92,12 @@ Metrics/PerceivedComplexity: Max: 40 RSpec/ExampleLength: Max: 310 +# Autocorrect mangles short-style Hash tags with nested generics/parens +# (e.g. Hash{Array(String, Array) => String}) into invalid syntax. +# Confirmed broken through rubocop-yard 1.3.0 (latest as of this writing), +# against yard 0.9.45 (also latest as of this writing). +YARD/CollectionStyle: + Enabled: false plugins: - rubocop-rspec diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 298a62390..ef068ee74 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -800,6 +800,15 @@ def qualify_superclass fq_sub_tag store.qualify_superclass fq_sub_tag end + # @param require_path [String] + # + # @return [Array, nil] + def resolve_require require_path + raise "Unable to resolve require '#{require_path}' without a workspace" if workspace.nil? + + Workspace::Gemspecs.new(workspace.directory).resolve_require require_path + end + private # A hash of source maps with filename keys. diff --git a/lib/solargraph/diagnostics/base.rb b/lib/solargraph/diagnostics/base.rb index ff91a9062..31b9a4342 100644 --- a/lib/solargraph/diagnostics/base.rb +++ b/lib/solargraph/diagnostics/base.rb @@ -20,8 +20,9 @@ def initialize *args # # @param source [Solargraph::Source] # @param api_map [Solargraph::ApiMap] + # @param workspace [Solargraph::Workspace, nil] # @return [Array] - def diagnose source, api_map + def diagnose source, api_map, workspace: nil [] end end diff --git a/lib/solargraph/diagnostics/type_check.rb b/lib/solargraph/diagnostics/type_check.rb index b1333f9d9..12d4f6c42 100644 --- a/lib/solargraph/diagnostics/type_check.rb +++ b/lib/solargraph/diagnostics/type_check.rb @@ -7,12 +7,12 @@ module Diagnostics # class TypeCheck < Base # @return [Array] - def diagnose source, api_map + def diagnose source, api_map, workspace: nil # return [] unless args.include?('always') || api_map.workspaced?(source.filename) severity = Diagnostics::Severities::ERROR level = args.reverse.find { |a| %w[normal typed strict strong].include?(a) } || :normal # @sg-ignore sensitive typing needs to handle || on nil types - checker = Solargraph::TypeChecker.new(source.filename, api_map: api_map, level: level.to_sym) + checker = Solargraph::TypeChecker.new(source.filename, api_map: api_map, level: level.to_sym, workspace: workspace) checker.problems .sort { |a, b| a.location.range.start.line <=> b.location.range.start.line } .map do |problem| diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index 849da9368..e9d32bfca 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -342,12 +342,12 @@ def preference_map @preference_map ||= preferences.to_h { |gemspec| [gemspec.name, gemspec] } end - # @param gemspec [Gem::Specification] + # @param gemspec [Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification] # # @return [Gem::Specification] def gemspec_or_preference gemspec - return gemspec unless preference_map.key?(gemspec.name) - return gemspec if gemspec.version == preference_map[gemspec.name].version + return to_gem_specification(gemspec) unless preference_map.key?(gemspec.name) + return to_gem_specification(gemspec) if gemspec.version == preference_map[gemspec.name].version change_gemspec_version gemspec, preference_map[gemspec.name].version end diff --git a/spec/api_map_method_spec.rb b/spec/api_map_method_spec.rb index 063b22f32..b0f60ac2a 100644 --- a/spec/api_map_method_spec.rb +++ b/spec/api_map_method_spec.rb @@ -118,14 +118,21 @@ class B end describe '#get_method_stack' do - let(:out) { StringIO.new } - let(:api_map) { described_class.load_with_cache(Dir.pwd, out) } + let(:api_map) { described_class.load('') } context 'with stdlib that has vital dependencies' do let(:external_requires) { ['yaml'] } let(:method_stack) { api_map.get_method_stack('YAML', 'safe_load', scope: :class) } it 'handles the YAML gem aliased to Psych' do + # catalog first so doc_map registers 'yaml' as required before we + # try to cache it - cache_gem is a no-op for gems doc_map doesn't + # yet know it needs + api_map.catalog bench + specs = api_map.resolve_require('yaml') + specs.each { |spec| api_map.cache_gem(spec) } + api_map.catalog bench + expect(method_stack).not_to be_empty end end @@ -135,6 +142,15 @@ class B let(:method_stack) { api_map.get_method_stack('Thor', 'desc', scope: :class) } it 'handles finding Thor.desc' do + # catalog first so doc_map registers 'thor' as required before we + # try to cache it - cache_gem is a no-op for gems doc_map doesn't + # yet know it needs + api_map.catalog bench + specs = api_map.resolve_require('thor') + specs.each { |spec| api_map.cache_gem(spec) } + api_map.catalog bench + + # if this fails you may not have an rbs collection installed expect(method_stack).not_to be_empty end end diff --git a/spec/language_server/message/extended/check_gem_version_spec.rb b/spec/language_server/message/extended/check_gem_version_spec.rb index 26023f505..02f0b1c4a 100644 --- a/spec/language_server/message/extended/check_gem_version_spec.rb +++ b/spec/language_server/message/extended/check_gem_version_spec.rb @@ -36,6 +36,10 @@ end it 'responds to update actions' do + status = instance_double(Process::Status) + allow(status).to receive(:==).with(0).and_return(true) + allow(Open3).to receive(:capture2).with('gem update solargraph').and_return(['', status]) + host = Solargraph::LanguageServer::Host.new message = described_class.new(host, {}, current: Gem::Version.new('0.0.1')) message.process @@ -52,6 +56,7 @@ } host.receive action end.not_to raise_error + expect(Open3).to have_received(:capture2).with('gem update solargraph') end it 'uses bundler' do diff --git a/spec/library_spec.rb b/spec/library_spec.rb index 9f9ab87dc..a21030c12 100644 --- a/spec/library_spec.rb +++ b/spec/library_spec.rb @@ -59,7 +59,7 @@ def foo(adapter) end it 'returns a Completion' do - library = described_class.new(Solargraph::Workspace.new(Dir.pwd, + library = described_class.new(Solargraph::Workspace.new('', Solargraph::Workspace::Config.new)) library.attach Solargraph::Source.load_string(%( require 'backport' diff --git a/spec/pin/base_spec.rb b/spec/pin/base_spec.rb index e11566d38..548f3bbc9 100644 --- a/spec/pin/base_spec.rb +++ b/spec/pin/base_spec.rb @@ -52,8 +52,18 @@ end it 'deals well with known closure combination issue' do - Solargraph::Shell.new.uncache('yard') - api_map = Solargraph::ApiMap.load_with_cache('.', $stderr) + # if this fails you might not have an rbs collection installed + api_map = Solargraph::ApiMap.load '' + + # catalog first so doc_map registers 'yard' as required before we + # try to cache it - cache_gem is a no-op for gems doc_map doesn't + # yet know it needs + bench = Solargraph::Bench.new(external_requires: ['yard']) + api_map.catalog bench + spec = Gem::Specification.find_by_name('yard') + api_map.cache_gem(spec) + api_map.catalog bench + pins = api_map.get_method_stack('YARD::Docstring', 'parser', scope: :class) expect(pins.length).to eq(1) parser_method_pin = pins.first diff --git a/spec/pin/method_spec.rb b/spec/pin/method_spec.rb index 6c07ced6d..de2d4d835 100644 --- a/spec/pin/method_spec.rb +++ b/spec/pin/method_spec.rb @@ -518,7 +518,9 @@ class Foo # on type. Let's make sure we combine those with anything else # found (e.g., additions from the BigDecimal RBS collection) # without collapsing signatures - api_map = Solargraph::ApiMap.load_with_cache(Dir.pwd, nil) + api_map = Solargraph::ApiMap.load(Dir.pwd) + bench = Solargraph::Bench.new external_requires: ['bigdecimal'] + api_map.catalog(bench) method = api_map.get_method_stack('Integer', '+', scope: :instance).first expect(method.signatures.count).to be > 3 end diff --git a/spec/position_spec.rb b/spec/position_spec.rb index d61b05ce5..300973a15 100644 --- a/spec/position_spec.rb +++ b/spec/position_spec.rb @@ -1,5 +1,3 @@ -# frozen_string_literal: true - describe Solargraph::Position do it 'normalizes arrays into positions' do pos = described_class.normalize([0, 1]) diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index 50f4b0b1a..c76032a88 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -97,7 +97,12 @@ def bar: () -> untyped context 'with standard loads for solargraph project' do before :all do # rubocop:disable RSpec/BeforeAfterAll - @api_map = Solargraph::ApiMap.load_with_cache('.') + @api_map = Solargraph::ApiMap.load('.') + gems = %w[parser ast open3] + bench = Solargraph::Bench.new(workspace: @api_map.workspace, external_requires: gems) + @api_map.catalog(bench) + @api_map.cache_all_for_doc_map! + @api_map.catalog(bench) end let(:api_map) { @api_map } @@ -160,7 +165,9 @@ class Sub < Hash[Symbol, untyped] if Gem::Version.new(RBS::VERSION) >= Gem::Version.new('3.9.1') context 'with method pin for Open3.capture2e' do it 'accepts chdir kwarg' do - api_map = Solargraph::ApiMap.load_with_cache('.', $stdout) + api_map = Solargraph::ApiMap.load('.') + bench = Solargraph::Bench.new(external_requires: ['open3']) + api_map.catalog(bench) method_pin = api_map.pins.find do |pin| pin.is_a?(Solargraph::Pin::Method) && pin.path == 'Open3.capture2e' diff --git a/spec/yard_map/mapper_spec.rb b/spec/yard_map/mapper_spec.rb index b2efd4cec..1114806dd 100644 --- a/spec/yard_map/mapper_spec.rb +++ b/spec/yard_map/mapper_spec.rb @@ -37,13 +37,6 @@ def pins_with require expect(pins.map(&:return_type).uniq.map(&:to_s)).to eq(['self']) end - it 'marks correct return type from RuboCop::Options.new' do - # Using rubocop because it's a known dependency - pins = pins_with('rubocop').select { |pin| pin.path == 'RuboCop::Options.new' } - expect(pins.map(&:return_type).uniq.map(&:to_s)).to eq(['self']) - expect(pins.flat_map(&:signatures).map(&:return_type).uniq.map(&:to_s)).to eq(['self']) - end - it 'marks non-explicit methods' do # Using rspec-expectations because it's a known dependency pin = pins_with('rspec/expectations').find { |pin| pin.path == 'RSpec::Matchers#expect' }