lib/rubocop/cop/rails/strong_parameters_expect.rb
# frozen_string_literal: true module RuboCop module Cop module Rails # Enforces the use of `ActionController::Parameters#expect` as a method for strong parameter handling. # # @safety # This cop's autocorrection is considered unsafe because there are cases where the HTTP status may change # from 500 to 400 when handling invalid parameters. This change, however, reflects an intentional # incompatibility introduced for valid reasons by the `expect` method, which aligns better with # strong parameter conventions. # # @example # # # bad # params.require(:user).permit(:name, :age) # params.permit(user: [:name, :age]).require(:user) # # # good # params.expect(user: [:name, :age]) # class StrongParametersExpect < Base extend AutoCorrector extend TargetRailsVersion MSG = 'Use `%<prefer>s` instead.' RESTRICT_ON_SEND = %i[require permit].freeze minimum_target_rails_version 8.0 def_node_matcher :params_require_permit, <<~PATTERN $(call $(call (send nil? :params) :require _) :permit _+) PATTERN def_node_matcher :params_permit_require, <<~PATTERN $(call $(call (send nil? :params) :permit (hash (pair _require_param_name _ ))) :require _require_param_name) PATTERN # rubocop:disable Metrics/AbcSize, Metrics/MethodLength def on_send(node) return if part_of_ignored_node?(node) if (permit_method, require_method = params_require_permit(node)) range = offense_range(require_method, node) prefer = expect_method(require_method, permit_method) replace_argument = true elsif (require_method, permit_method = params_permit_require(node)) range = offense_range(permit_method, node) prefer = "expect(#{permit_method.arguments.map(&:source).join(', ')})" replace_argument = false else return end add_offense(range, message: format(MSG, prefer: prefer)) do |corrector| corrector.remove(require_method.receiver.source_range.end.join(require_method.source_range.end)) corrector.replace(permit_method.loc.selector, 'expect') if replace_argument corrector.insert_before(permit_method.first_argument, "#{require_key(require_method)}[") corrector.insert_after(permit_method.last_argument, ']') end end ignore_node(node) end # rubocop:enable Metrics/AbcSize, Metrics/MethodLength alias on_csend on_send private def offense_range(method_node, node) method_node.loc.selector.join(node.source_range.end) end def expect_method(require_method, permit_method) require_key = require_key(require_method) permit_args = permit_method.arguments.map(&:source).join(', ') arguments = "#{require_key}[#{permit_args}]" "expect(#{arguments})" end def require_key(require_method) if (first_argument = require_method.first_argument).respond_to?(:value) require_arg = first_argument.value separator = ': ' else require_arg = first_argument.source separator = ' => ' end "#{require_arg}#{separator}" end end end end end