diff --git a/lib/solargraph/rspec/convention.rb b/lib/solargraph/rspec/convention.rb index 9bd94bb..5dff9df 100644 --- a/lib/solargraph/rspec/convention.rb +++ b/lib/solargraph/rspec/convention.rb @@ -109,26 +109,23 @@ def local(source_map) namespace_pins: namespace_pins, rspec_walker: rspec_walker ).correct(source_map) do |pins_to_add| - pins += pins_to_add + pins_to_add.each { |pin| pins << pin } end Correctors::ExampleAndHookBlocksBindingCorrector.new( namespace_pins: namespace_pins, rspec_walker: rspec_walker ).correct(source_map) do |pins_to_add| - pins += pins_to_add + pins_to_add.each { |pin| pins << pin } end - # @type [Pin::Method, nil] - described_class_pin = nil Correctors::DescribedClassCorrector.new( namespace_pins: namespace_pins, rspec_walker: rspec_walker ).correct( source_map ) do |pins_to_add| - described_class_pin = pins_to_add.first - pins += pins_to_add + pins_to_add.each { |pin| pins << pin } end Correctors::LetMethodsCorrector.new( @@ -137,26 +134,24 @@ def local(source_map) ).correct( source_map ) do |pins_to_add| - pins += pins_to_add + pins_to_add.each { |pin| pins << pin } end - # @type [Pin::Method, nil] - subject_pin = nil Correctors::SubjectMethodCorrector.new( namespace_pins: namespace_pins, - rspec_walker: rspec_walker + rspec_walker: rspec_walker, + added_pins: pins ).correct( source_map ) do |pins_to_add| - subject_pin = pins_to_add.first - pins += pins_to_add + pins_to_add.each { |pin| pins << pin } end Correctors::ContextBlockMethodsCorrector.new( namespace_pins: namespace_pins, rspec_walker: rspec_walker ).correct(source_map) do |pins_to_add| - pins += pins_to_add + pins_to_add.each { |pin| pins << pin } end Correctors::DslMethodsCorrector.new( @@ -166,25 +161,12 @@ def local(source_map) ).correct( source_map ) do |pins_to_add| - pins += pins_to_add + pins_to_add.each { |pin| pins << pin } end rspec_walker.walk! pins += namespace_pins - # Implicit subject - if !subject_pin && described_class_pin - Correctors::ImplicitSubjectMethodCorrector.new( - namespace_pins: namespace_pins, - described_class_pin: described_class_pin - ).correct( - source_map - ) do |pins_to_add| - subject_pin = pins_to_add.first - pins += pins_to_add - end - end - if pins.any? Solargraph.logger.debug( "[RSpec] added #{pins.map(&:inspect)} to #{source_map.filename}" diff --git a/lib/solargraph/rspec/correctors/subject_method_corrector.rb b/lib/solargraph/rspec/correctors/subject_method_corrector.rb index 6a3a20f..dabd60f 100644 --- a/lib/solargraph/rspec/correctors/subject_method_corrector.rb +++ b/lib/solargraph/rspec/correctors/subject_method_corrector.rb @@ -7,18 +7,68 @@ module Rspec module Correctors # Defines let-like methods in the example group block class SubjectMethodCorrector < LetMethodsCorrector + # @return [Array] + attr_reader :added_pins + + # @param namespace_pins [Array] + # @param added_pins [Array] + # @param rspec_walker [Solargraph::Rspec::SpecWalker] + def initialize(namespace_pins:, rspec_walker:, added_pins:) + super(namespace_pins: namespace_pins, rspec_walker: rspec_walker) + + @added_pins = added_pins + end + # @param source_map [Solargraph::SourceMap] # @return [void] def correct(_source_map) rspec_walker.on_subject do |subject_name, location_range, fake_method_ast| - next unless subject_name - namespace_pin = closest_namespace_pin(namespace_pins, location_range.start.line) next unless namespace_pin - subject_pin = rspec_let_method(namespace_pin, subject_name, location_range, fake_method_ast) + subject_pin = rspec_subject_method(namespace_pin, subject_name, location_range, fake_method_ast) yield [subject_pin].compact if block_given? end + + rspec_walker.after_walk do + next unless described_class_pin + + namespace_pin = closest_namespace_pin(namespace_pins, described_class_pin.location.range.start.line) + + yield [implicit_subject_pin(described_class_pin, namespace_pin)] if block_given? && namespace_pin + end + end + + private + + # @return [Pin::Method, nil] + def described_class_pin + @described_class_pin ||= added_pins.find { |pin| pin.is_a?(Pin::Method) && pin.name == 'described_class' } + end + + # @param namespace_pin [Pin::Namespace] + # @param subject_name [String, nil] + # @param location_range [Solargraph::Range] + # @param fake_method_ast [Parser::AST::Node] + # @return [Pin::Method] + def rspec_subject_method(namespace_pin, subject_name, location_range, fake_method_ast) + method_name = subject_name || 'subject' + rspec_let_method(namespace_pin, method_name, location_range, fake_method_ast) + end + + # @param described_class_pin [Pin::Method] + # @param namespace_pin [Pin::Namespace] + # @return [Pin::Method] + def implicit_subject_pin(described_class_pin, namespace_pin) + described_class = described_class_pin.return_type.first.subtypes.first.name + + PinFactory.build_public_method( + namespace_pin, + 'subject', + types: ["::#{described_class}"], + location: described_class_pin.location, + scope: :instance + ) end end end diff --git a/spec/solargraph/rspec/convention_spec.rb b/spec/solargraph/rspec/convention_spec.rb index 3220647..5a7d813 100644 --- a/spec/solargraph/rspec/convention_spec.rb +++ b/spec/solargraph/rspec/convention_spec.rb @@ -101,12 +101,26 @@ it 'should do something' do sub end + + context 'nested context with nameless subject' do + subject { 1 } + + it 'overrides the implicit subject' do + sub + end + end end RUBY assert_public_instance_method(api_map, 'RSpec::ExampleGroups::SomeNamespaceTransaction#subject', ['SomeNamespace::Transaction']) expect(completion_at(filename, [2, 6])).to include('subject') + assert_public_instance_method_inferred_type( + api_map, + 'RSpec::ExampleGroups::SomeNamespaceTransaction::NestedContext#subject', + 'Integer' + ) + expect(completion_at(filename, [9, 9])).to include('subject') end it 'generates modules for describe/context blocks' do diff --git a/spec/support/solargraph_helpers.rb b/spec/support/solargraph_helpers.rb index aaf3617..6eb261f 100644 --- a/spec/support/solargraph_helpers.rb +++ b/spec/support/solargraph_helpers.rb @@ -17,7 +17,7 @@ def load_sources(*sources) def assert_public_instance_method(map, query, return_type) pin = find_pin(query, map) - expect(pin).to_not be_nil + expect(pin).to_not be_nil, "Method #{query} not found" expect(pin.scope).to eq(:instance) expect(pin.return_type.map(&:tag)).to eq(return_type) @@ -26,7 +26,7 @@ def assert_public_instance_method(map, query, return_type) def assert_public_instance_method_inferred_type(map, query, return_type) pin = find_pin(query, map) - expect(pin).to_not be_nil + expect(pin).to_not be_nil, "Method #{query} not found" expect(pin.scope).to eq(:instance) inferred_return_type = pin.probe(api_map).tag