lib/rubocop/cop/rails/response_parsed_body.rb



# frozen_string_literal: true

module RuboCop
  module Cop
    module Rails
      # Prefer `response.parsed_body` to custom parsing logic for `response.body`.
      #
      # @safety
      #   This cop is unsafe because Content-Type may not be `application/json` or `text/html`.
      #   For example, the proprietary Content-Type provided by corporate entities such as
      #   `application/vnd.github+json` is not supported at `response.parsed_body` by default,
      #   so you still have to use `JSON.parse(response.body)` there.
      #
      # @example
      #   # bad
      #   JSON.parse(response.body)
      #
      #   # bad
      #   Nokogiri::HTML.parse(response.body)
      #
      #   # bad
      #   Nokogiri::HTML5.parse(response.body)
      #
      #   # good
      #   response.parsed_body
      class ResponseParsedBody < Base
        extend AutoCorrector
        extend TargetRailsVersion

        RESTRICT_ON_SEND = %i[parse].freeze

        minimum_target_rails_version 5.0

        # @!method json_parse_response_body?(node)
        def_node_matcher :json_parse_response_body?, <<~PATTERN
          (send
            (const {nil? cbase} :JSON)
            :parse
            (send
              (send nil? :response)
              :body
            )
          )
        PATTERN

        # @!method nokogiri_html_parse_response_body(node)
        def_node_matcher :nokogiri_html_parse_response_body, <<~PATTERN
          (send
            (const
              (const {nil? cbase} :Nokogiri)
              ${:HTML :HTML5}
            )
            :parse
            (send
              (send nil? :response)
              :body
            )
          )
        PATTERN

        def on_send(node)
          check_json_parse_response_body(node)

          return unless target_rails_version >= 7.1

          check_nokogiri_html_parse_response_body(node)
        end

        private

        def autocorrect(corrector, node)
          corrector.replace(node, 'response.parsed_body')
        end

        def check_json_parse_response_body(node)
          return unless json_parse_response_body?(node)

          add_offense(
            node,
            message: 'Prefer `response.parsed_body` to `JSON.parse(response.body)`.'
          ) do |corrector|
            autocorrect(corrector, node)
          end
        end

        def check_nokogiri_html_parse_response_body(node)
          return unless (const = nokogiri_html_parse_response_body(node))

          add_offense(
            node,
            message: "Prefer `response.parsed_body` to `Nokogiri::#{const}.parse(response.body)`."
          ) do |corrector|
            autocorrect(corrector, node)
          end
        end
      end
    end
  end
end