Skip to content

Commit

Permalink
Fix subject without name overrides implicit subject
Browse files Browse the repository at this point in the history
When defining a `subject {  }` without name inside a sub-context,
the subject method was using the implicit (root subject) instead of
sub-context one.

```ruby
RSpec.describe Tag, type: :model do
  it 'does something' do
    subject
  end

  context 'some context' do
    subject { 1 }

    it 'does something' do
      subject # => type Tag instead of Integer
    end
  end
end
```
  • Loading branch information
lekemula committed May 26, 2024
1 parent 278671b commit dbc985f
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 32 deletions.
36 changes: 9 additions & 27 deletions lib/solargraph/rspec/convention.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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(
Expand All @@ -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}"
Expand Down
56 changes: 53 additions & 3 deletions lib/solargraph/rspec/correctors/subject_method_corrector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,68 @@ module Rspec
module Correctors
# Defines let-like methods in the example group block
class SubjectMethodCorrector < LetMethodsCorrector
# @return [Array<Pin::Base>]
attr_reader :added_pins

# @param namespace_pins [Array<Pin::Namespace>]
# @param added_pins [Array<Pin::Base>]
# @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
Expand Down
14 changes: 14 additions & 0 deletions spec/solargraph/rspec/convention_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions spec/support/solargraph_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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

Expand Down

0 comments on commit dbc985f

Please sign in to comment.