|
| 1 | +# frozen_string_literal: true |
| 2 | + |
| 3 | +module RuboCop |
| 4 | + module Cop |
| 5 | + module Rails |
| 6 | + # Enforces the use of the current HTTP status names instead of deprecated ones. |
| 7 | + # |
| 8 | + # @example |
| 9 | + # # bad |
| 10 | + # render json: { error: "Invalid data" }, status: :unprocessable_entity |
| 11 | + # head :payload_too_large |
| 12 | + # |
| 13 | + # # good |
| 14 | + # render json: { error: "Invalid data" }, status: :unprocessable_content |
| 15 | + # head :content_too_large |
| 16 | + # |
| 17 | + class DeprecatedHttpStatusNames < Base |
| 18 | + extend AutoCorrector |
| 19 | + |
| 20 | + requires_gem 'rack', '>= 3.1.0' |
| 21 | + |
| 22 | + RESTRICT_ON_SEND = %i[render redirect_to head assert_response assert_redirected_to].freeze |
| 23 | + |
| 24 | + DEPRECATED_STATUSES = { |
| 25 | + unprocessable_entity: :unprocessable_content, |
| 26 | + payload_too_large: :content_too_large |
| 27 | + }.freeze |
| 28 | + |
| 29 | + MSG = 'Use `:%<preferred>s` instead of `:%<deprecated>s`. The `:%<deprecated>s` status is deprecated.' |
| 30 | + |
| 31 | + def_node_matcher :status_method_call, <<~PATTERN |
| 32 | + { |
| 33 | + (send nil? {:render :redirect_to} _ $hash) |
| 34 | + (send nil? {:render :redirect_to} $hash) |
| 35 | + (send nil? {:head :assert_response} $_ ...) |
| 36 | + (send nil? :assert_redirected_to _ $hash ...) |
| 37 | + (send nil? :assert_redirected_to $hash ...) |
| 38 | + } |
| 39 | + PATTERN |
| 40 | + |
| 41 | + def_node_matcher :status_hash_value, <<~PATTERN |
| 42 | + (hash <(pair (sym :status) $_) ...>) |
| 43 | + PATTERN |
| 44 | + |
| 45 | + def on_send(node) |
| 46 | + status_method_call(node) do |status_node| |
| 47 | + if status_node.hash_type? |
| 48 | + # Handle hash arguments like { status: :unprocessable_entity } |
| 49 | + status_hash_value(status_node) do |status_value| |
| 50 | + find_deprecated_status_names(status_value) |
| 51 | + end |
| 52 | + else |
| 53 | + # Handle positional arguments like head :unprocessable_entity |
| 54 | + find_deprecated_status_names(status_node) |
| 55 | + end |
| 56 | + end |
| 57 | + end |
| 58 | + |
| 59 | + private |
| 60 | + |
| 61 | + def find_deprecated_status_names(node) |
| 62 | + if node.sym_type? && DEPRECATED_STATUSES.key?(node.value) |
| 63 | + deprecated_status = node.value |
| 64 | + preferred_status = DEPRECATED_STATUSES[deprecated_status] |
| 65 | + |
| 66 | + message = format(MSG, deprecated: deprecated_status, preferred: preferred_status) |
| 67 | + |
| 68 | + add_offense(node, message: message) do |corrector| |
| 69 | + corrector.replace(node, ":#{preferred_status}") |
| 70 | + end |
| 71 | + elsif node.respond_to?(:children) |
| 72 | + # Recursively search child nodes (handles ternary expressions, etc.) |
| 73 | + node.children.each do |child| |
| 74 | + find_deprecated_status_names(child) if child.is_a?(Parser::AST::Node) |
| 75 | + end |
| 76 | + end |
| 77 | + end |
| 78 | + end |
| 79 | + end |
| 80 | + end |
| 81 | +end |
0 commit comments