Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Fix a false positive for `RSpec/ContainExactly` when `contain_exactly` has multiple splat arguments. ([@ydah])
- Add autocorrect support for `RSpec/SubjectDeclaration`. ([@eugeneius])
- Fix false negatives for `RSpec/SpecFilePathFormat` when the expected class path only partially matches a path segment. ([@ydah])
- Fix a false negative for `RSpec/ExpectActual` when the matcher takes no arguments (e.g. `expect("foo").to be_present`, `expect(1).to be`). ([@cvx])

## 3.9.0 (2026-01-07)

Expand Down Expand Up @@ -997,6 +998,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
[@composerinteralia]: https://github.com/composerinteralia
[@corsonknowles]: https://github.com/corsonknowles
[@corydiamand]: https://github.com/corydiamand
[@cvx]: https://github.com/cvx
[@d4rky-pl]: https://github.com/d4rky-pl
[@darhazer]: https://github.com/Darhazer
[@daveworth]: https://github.com/daveworth
Expand Down
2 changes: 2 additions & 0 deletions docs/modules/ROOT/pages/cops_rspec.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2178,7 +2178,9 @@ expect(pattern).to eq(/foo/)
expect(name).to eq("John")

# bad (not supported autocorrection)
expect(42).to be_even
expect(false).to eq(true)
expect("user").to be_present
----

[#configurable-attributes-rspecexpectactual]
Expand Down
46 changes: 33 additions & 13 deletions lib/rubocop/cop/rspec/expect_actual.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ module RSpec
# expect(name).to eq("John")
#
# # bad (not supported autocorrection)
# expect(42).to be_even
# expect(false).to eq(true)
# expect("user").to be_present
#
class ExpectActual < Base
extend AutoCorrector

MSG = 'Provide the actual value you are testing to `expect(...)`.'
MSG_NO_ARG = 'Test a non-literal value with `expect(...)`.'

RESTRICT_ON_SEND = Runners.all

Expand Down Expand Up @@ -65,26 +68,43 @@ class ExpectActual < Base
)
PATTERN

# @!method expect_literal_no_arg(node)
def_node_matcher :expect_literal_no_arg, <<~PATTERN
(send
(send nil? :expect $#literal?)
#Runners.all
$(send nil? $_)
)
PATTERN

def on_send(node)
expect_literal(node) do |actual, send_node, matcher, expected|
next if SKIPPED_MATCHERS.include?(matcher)

add_offense(actual) do |corrector|
next unless CORRECTABLE_MATCHERS.include?(matcher)
next if literal?(expected)

corrector.replace(actual, expected.source)
if matcher == :be
corrector.replace(expected, actual.source)
else
corrector.replace(send_node, "#{matcher}(#{actual.source})")
end
end
register_offense(actual, send_node, matcher, expected)
end
expect_literal_no_arg(node) do |actual, send_node, matcher|
register_offense(actual, send_node, matcher, nil)
end
end

private

def register_offense(actual, send_node, matcher, expected)
return if SKIPPED_MATCHERS.include?(matcher)

message = expected.nil? ? MSG_NO_ARG : MSG
add_offense(actual, message: message) do |corrector|
next unless CORRECTABLE_MATCHERS.include?(matcher)
next if expected.nil? || literal?(expected)

corrector.replace(actual, expected.source)
if matcher == :be
corrector.replace(expected, actual.source)
else
corrector.replace(send_node, "#{matcher}(#{actual.source})")
end
end
end

# This is not implemented using a NodePattern because it seems
# to not be able to match against an explicit (nil) sexp
def literal?(node)
Expand Down
24 changes: 21 additions & 3 deletions spec/rubocop/cop/rspec/expect_actual_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -227,14 +227,17 @@
RUBY
end

it 'ignores `be` with no argument' do
expect_no_offenses(<<~RUBY)
it 'flags `be` with no argument' do
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This case wasn't supported only to simplify implementation afaict (see #858)

expect_offense(<<~RUBY)
describe Foo do
it 'uses expect legitimately' do
it 'uses expect incorrectly' do
expect(1).to be
^ Test a non-literal value with `expect(...)`.
end
end
RUBY

expect_no_corrections
end

it 'flags `be` with an argument' do
Expand Down Expand Up @@ -339,6 +342,21 @@
expect_no_corrections
end

it 'flags literal values with argumentless predicate matchers' do
expect_offense(<<~RUBY)
describe Foo do
it 'uses expect incorrectly' do
expect("user").to be_present
^^^^^^ Test a non-literal value with `expect(...)`.
expect([]).to be_empty
^^ Test a non-literal value with `expect(...)`.
end
end
RUBY

expect_no_corrections
end

it 'does not flag when the matcher is a rails routing matcher' do
expect_no_offenses(<<~RUBY)
describe Foo do
Expand Down