1+ # frozen_string_literal: true
2+
3+ module RuboCop
4+ module Cop
5+ module Custom
6+ # This cop checks for uses of `Rack::Utils.status_code` in RSpec tests
7+ # and suggests using `have_http_status` instead.
8+ #
9+ # @example
10+ # # bad
11+ # expect(response.status).to be(Rack::Utils.status_code(:created))
12+ #
13+ # # good
14+ # expect(response).to have_http_status(:created)
15+ class RackUtilsStatusCode < RuboCop ::Cop ::Base
16+ extend AutoCorrector
17+
18+ MSG = 'Prefer `have_http_status` over `Rack::Utils.status_code`.'
19+
20+ def_node_matcher :rack_utils_status_code? , <<~PATTERN
21+ (send
22+ (const
23+ (const nil? :Rack) :Utils) :status_code $_)
24+ PATTERN
25+
26+ def_node_matcher :expect_response_status_pattern? , <<~PATTERN
27+ (send
28+ (send nil? :expect
29+ (send $(send nil? _) :status)
30+ )
31+ $_
32+ (send nil? {:be :eq :eql :equal}
33+ (send
34+ (const
35+ (const nil? :Rack) :Utils) :status_code $_)))
36+ PATTERN
37+
38+ def on_send ( node )
39+ # Only proceed if we're in an expect statement
40+ expect_node = find_expect_node ( node )
41+ return unless expect_node
42+
43+ rack_utils_status_code? ( node ) do |status_arg |
44+ add_offense ( node , message : MSG ) do |corrector |
45+ expect_response_status_pattern? ( expect_node ) do |response_obj , to_method , status_sym |
46+ # Replace just the Rack::Utils.status_code part with have_http_status
47+ replacement = "expect(#{ response_obj . source } ).#{ to_method } have_http_status(#{ status_sym . source } )"
48+ corrector . replace ( expect_node , replacement )
49+ end
50+ end
51+ end
52+ end
53+
54+ private
55+
56+ # Find the expect(...).to be(Rack::Utils.status_code(...)) node
57+ def find_expect_node ( node )
58+ return nil unless node
59+
60+ if node . send_type? &&
61+ ( node . method_name == :to || node . method_name == :to_not || node . method_name == :not_to ) &&
62+ node . receiver &.send_type? &&
63+ node . receiver . method_name == :expect &&
64+ node . first_argument &.send_type? &&
65+ ( node . first_argument . method_name == :be ||
66+ node . first_argument . method_name == :eq ||
67+ node . first_argument . method_name == :eql ||
68+ node . first_argument . method_name == :equal )
69+
70+ return node
71+ end
72+
73+ if node . parent
74+ find_expect_node ( node . parent )
75+ else
76+ nil
77+ end
78+ end
79+ end
80+ end
81+ end
82+ end
0 commit comments