diff --git a/CHANGELOG.md b/CHANGELOG.md
index 32524fb..b3b2eee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
+- Implement RSpec [one-liner syntax](https://rspec.info/features/3-12/rspec-core/subject/one-liner-syntax/) helpers: `is_expected`, `should` and `should_not`
+ - 📹
+
+
## v0.3.0 - 2024-07-10
diff --git a/doc/images/one_liners_demo.gif b/doc/images/one_liners_demo.gif
new file mode 100644
index 0000000..478688e
Binary files /dev/null and b/doc/images/one_liners_demo.gif differ
diff --git a/lib/solargraph/rspec/correctors/subject_method_corrector.rb b/lib/solargraph/rspec/correctors/subject_method_corrector.rb
index 62d0a38..409a304 100644
--- a/lib/solargraph/rspec/correctors/subject_method_corrector.rb
+++ b/lib/solargraph/rspec/correctors/subject_method_corrector.rb
@@ -16,6 +16,7 @@ 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
@@ -23,7 +24,11 @@ def correct(_source_map)
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
@@ -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(
@@ -58,6 +63,41 @@ def implicit_subject_pin(described_class_pin, namespace_pin)
scope: :instance
)
end
+
+ # @param subject_pin [Pin::Method]
+ # @return [Array]
+ 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
diff --git a/spec/solargraph/rspec/convention_spec.rb b/spec/solargraph/rspec/convention_spec.rb
index b65155e..c94c945 100644
--- a/spec/solargraph/rspec/convention_spec.rb
+++ b/spec/solargraph/rspec/convention_spec.rb
@@ -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