Skip to content

Commit

Permalink
Implement RSpec one-liner helpers
Browse files Browse the repository at this point in the history
- is_expected
- should
- shuld_not
  • Loading branch information
lekemula committed Aug 25, 2024
1 parent 07f1394 commit 92a8b22
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 2 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
<img src="./doc/images/3rd_party_matchers.png" width="600" alt="3rd party matchers completion">
</details>

- Implement RSpec [one-liner syntax](https://rspec.info/features/3-12/rspec-core/subject/one-liner-syntax/) helpers: `is_expected`, `should` and `should_not`
- <details><summary>📹</summary>
<img src="./doc/images/one_liners_demo.gif" width="600" alt="One-liner syntax completion">
</details>

## v0.3.0 - 2024-07-10

Expand Down
Binary file added doc/images/one_liners_demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 42 additions & 2 deletions lib/solargraph/rspec/correctors/subject_method_corrector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,19 @@ def correct(_source_map)

subject_pin = rspec_subject_method(namespace_pin, subject_name, location_range, fake_method_ast)
add_pin(subject_pin)
add_pins(one_liner_expecation_pins(subject_pin))
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)

add_pin(implicit_subject_pin(described_class_pin, namespace_pin)) if namespace_pin
if namespace_pin
implicit_subject_pin = implicit_subject_method(described_class_pin, namespace_pin)
add_pin(implicit_subject_pin)
add_pins(one_liner_expecation_pins(implicit_subject_pin))
end
end
end

Expand All @@ -47,7 +52,7 @@ def rspec_subject_method(namespace_pin, subject_name, location_range, fake_metho
# @param described_class_pin [Pin::Method]
# @param namespace_pin [Pin::Namespace]
# @return [Pin::Method]
def implicit_subject_pin(described_class_pin, namespace_pin)
def implicit_subject_method(described_class_pin, namespace_pin)
described_class = described_class_pin.return_type.first.subtypes.first.name

PinFactory.build_public_method(
Expand All @@ -58,6 +63,41 @@ def implicit_subject_pin(described_class_pin, namespace_pin)
scope: :instance
)
end

# @param subject_pin [Pin::Method]
# @return [Array<Pin::Method>]
def one_liner_expecation_pins(subject_pin)
[
one_liner_expecation_pin(subject_pin.closure, :is_expected, subject_pin.location),
one_liner_expecation_pin(subject_pin.closure, :should, subject_pin.location),
one_liner_expecation_pin(subject_pin.closure, :should_not, subject_pin.location)
]
end

# @param namespace_pin [Pin::Namespace]
# @param method_name [:is_expected, :should, :should_not]
# @param location [Solargraph::Location]
# @return [Pin::Method]
def one_liner_expecation_pin(namespace_pin, method_name, location)
return_type = case method_name
when :is_expected
['::RSpec::Expectations::ExpectationTarget']
when :should
['::RSpec::Matchers::BuiltIn::PositiveOperatorMatcher']
when :should_not
['::RSpec::Matchers::BuiltIn::NegativeOperatorMatcher']
else
raise ArgumentError, "Unknown inline expectation method: #{method_name}"
end

PinFactory.build_public_method(
namespace_pin,
method_name.to_s,
types: [return_type],
location: location,
scope: :instance
)
end
end
end
end
Expand Down
33 changes: 33 additions & 0 deletions spec/solargraph/rspec/convention_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,39 @@
expect(completion_at(filename, [22, 8])).to include('nested_something')
end

# @see https://rspec.info/features/3-12/rspec-core/subject/one-liner-syntax/
it 'generates method for one-liner expectations' do
load_string filename, <<~RUBY
RSpec.describe SomeNamespace::Transaction, type: :model do
subject(:transaction) { described_class.new }
it { is_expected.to be_a(SomeNamespace::Transaction) }
it { should be_a(SomeNamespace::Transaction) }
it { should_not be_a(String) }
end
RUBY

subject_location = { start: { line: 1, character: 2 }, end: { line: 1, character: 23 } }

assert_public_instance_method(
api_map,
'RSpec::ExampleGroups::TestSomeNamespaceTransaction#is_expected',
['RSpec::Expectations::ExpectationTarget']
) { |pin| expect(pin.location.range.to_hash).to eq(subject_location) }

assert_public_instance_method(
api_map,
'RSpec::ExampleGroups::TestSomeNamespaceTransaction#should',
['RSpec::Matchers::BuiltIn::PositiveOperatorMatcher']
) { |pin| expect(pin.location.range.to_hash).to eq(subject_location) }

assert_public_instance_method(
api_map,
'RSpec::ExampleGroups::TestSomeNamespaceTransaction#should_not',
['RSpec::Matchers::BuiltIn::NegativeOperatorMatcher']
) { |pin| expect(pin.location.range.to_hash).to eq(subject_location) }
end

it 'generates let methods with do/end block' do
load_string filename, <<~RUBY
RSpec.describe SomeNamespace::Transaction, type: :model do
Expand Down

0 comments on commit 92a8b22

Please sign in to comment.