From 60199baeb7f0f922bace742c8f2e564ce7604217 Mon Sep 17 00:00:00 2001 From: r7kamura Date: Thu, 18 Jul 2024 05:22:35 +0900 Subject: [PATCH] Fix false-negative for `UnspecifiedException` when matcher is chained --- CHANGELOG.md | 2 ++ .../cop/rspec/unspecified_exception.rb | 35 +++++++++++-------- .../cop/rspec/unspecified_exception_spec.rb | 27 ++++++++++++++ 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a362eaaf..f13124989 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Master (Unreleased) +- Fix false-negative for `UnspecifiedException` when matcher is chained. ([@r7kamura]) + ## 3.0.3 (2024-07-12) - Add support for Unicode RIGHT SINGLE QUOTATION MARK in `RSpec/ExampleWording`. ([@jdufresne]) diff --git a/lib/rubocop/cop/rspec/unspecified_exception.rb b/lib/rubocop/cop/rspec/unspecified_exception.rb index c8bcf6bc9..ed49701a8 100644 --- a/lib/rubocop/cop/rspec/unspecified_exception.rb +++ b/lib/rubocop/cop/rspec/unspecified_exception.rb @@ -32,34 +32,39 @@ module RSpec # class UnspecifiedException < Base MSG = 'Specify the exception being captured' - RESTRICT_ON_SEND = %i[to].freeze - # @!method empty_raise_error_or_exception(node) - def_node_matcher :empty_raise_error_or_exception, <<~PATTERN - (send - (block - (send nil? :expect) ...) - :to - (send nil? {:raise_error :raise_exception}) - ) + RESTRICT_ON_SEND = %i[ + raise_exception + raise_error + ].freeze + + # @!method expect_to?(node) + def_node_matcher :expect_to?, <<~PATTERN + (send (block (send nil? :expect) ...) :to ...) PATTERN def on_send(node) return unless empty_exception_matcher?(node) - add_offense(node.children.last) + add_offense(node) end private def empty_exception_matcher?(node) - empty_raise_error_or_exception(node) && !block_with_args?(node.parent) - end + return false if node.arguments? || node.block_literal? - def block_with_args?(node) - return false unless node&.block_type? + expect_to = find_expect_to(node) + return false unless expect_to + return false if expect_to.block_node&.arguments? + + true + end - node.arguments? + def find_expect_to(node) + node.each_ancestor(:send).find do |ancestor| + expect_to?(ancestor) + end end end end diff --git a/spec/rubocop/cop/rspec/unspecified_exception_spec.rb b/spec/rubocop/cop/rspec/unspecified_exception_spec.rb index c4e1a6037..bb5db00bd 100644 --- a/spec/rubocop/cop/rspec/unspecified_exception_spec.rb +++ b/spec/rubocop/cop/rspec/unspecified_exception_spec.rb @@ -171,5 +171,32 @@ }.to raise_exception(my_exception) RUBY end + + it 'detects chained offenses' do + expect_offense(<<~RUBY) + expect { + foo + }.to raise_exception.and change { bar } + ^^^^^^^^^^^^^^^ Specify the exception being captured + RUBY + end + + it 'detects more chained offenses' do + expect_offense(<<~RUBY) + expect { + foo + }.to raise_exception.and change { bar }.and change { baz } + ^^^^^^^^^^^^^^^ Specify the exception being captured + RUBY + end + + it 'detects more complex chained offenses' do + expect_offense(<<~RUBY) + expect { + foo + }.to change { bar }.and raise_exception.and change { baz } + ^^^^^^^^^^^^^^^ Specify the exception being captured + RUBY + end end end