|
| 1 | +# frozen_string_literal: true |
| 2 | + |
| 3 | +module RuboCop |
| 4 | + module Cop |
| 5 | + module Rails |
| 6 | + # Checks for Rails framework classes that are patched directly instead of using Active Support load hooks. Direct |
| 7 | + # patching forcibly loads the framework referenced, using hooks defers loading until it's actually needed. |
| 8 | + # |
| 9 | + # @safety |
| 10 | + # While using lazy load hooks is recommended, it changes the order in which is code is loaded and may reveal |
| 11 | + # load order dependency bugs. |
| 12 | + # |
| 13 | + # @example |
| 14 | + # |
| 15 | + # # bad |
| 16 | + # ActiveRecord::Base.include(MyClass) |
| 17 | + # |
| 18 | + # # good |
| 19 | + # ActiveSupport.on_load(:active_record) { include MyClass } |
| 20 | + class ActiveSupportOnLoad < Base |
| 21 | + extend AutoCorrector |
| 22 | + |
| 23 | + MSG = 'Use `%<prefer>s` instead of `%<current>s`.' |
| 24 | + RESTRICT_ON_SEND = %i[prepend include extend].freeze |
| 25 | + LOAD_HOOKS = { |
| 26 | + 'ActionCable' => 'action_cable', |
| 27 | + 'ActionCable::Channel::Base' => 'action_cable_channel', |
| 28 | + 'ActionCable::Connection::Base' => 'action_cable_connection', |
| 29 | + 'ActionCable::Connection::TestCase' => 'action_cable_connection_test_case', |
| 30 | + 'ActionController::API' => 'action_controller', |
| 31 | + 'ActionController::Base' => 'action_controller', |
| 32 | + 'ActionController::TestCase' => 'action_controller_test_case', |
| 33 | + 'ActionDispatch::IntegrationTest' => 'action_dispatch_integration_test', |
| 34 | + 'ActionDispatch::Request' => 'action_dispatch_request', |
| 35 | + 'ActionDispatch::Response' => 'action_dispatch_response', |
| 36 | + 'ActionDispatch::SystemTestCase' => 'action_dispatch_system_test_case', |
| 37 | + 'ActionMailbox::Base' => 'action_mailbox', |
| 38 | + 'ActionMailbox::InboundEmail' => 'action_mailbox_inbound_email', |
| 39 | + 'ActionMailbox::Record' => 'action_mailbox_record', |
| 40 | + 'ActionMailbox::TestCase' => 'action_mailbox_test_case', |
| 41 | + 'ActionMailer::Base' => 'action_mailer', |
| 42 | + 'ActionMailer::TestCase' => 'action_mailer_test_case', |
| 43 | + 'ActionText::Content' => 'action_text_content', |
| 44 | + 'ActionText::Record' => 'action_text_record', |
| 45 | + 'ActionText::RichText' => 'action_text_rich_text', |
| 46 | + 'ActionView::Base' => 'action_view', |
| 47 | + 'ActionView::TestCase' => 'action_view_test_case', |
| 48 | + 'ActiveJob::Base' => 'active_job', |
| 49 | + 'ActiveJob::TestCase' => 'active_job_test_case', |
| 50 | + 'ActiveRecord::Base' => 'active_record', |
| 51 | + 'ActiveStorage::Attachment' => 'active_storage_attachment', |
| 52 | + 'ActiveStorage::Blob' => 'active_storage_blob', |
| 53 | + 'ActiveStorage::Record' => 'active_storage_record', |
| 54 | + 'ActiveStorage::VariantRecord' => 'active_storage_variant_record', |
| 55 | + 'ActiveSupport::TestCase' => 'active_support_test_case' |
| 56 | + }.freeze |
| 57 | + |
| 58 | + def on_send(node) |
| 59 | + receiver, method, arguments = *node # rubocop:disable InternalAffairs/NodeDestructuring |
| 60 | + return unless receiver && (hook = LOAD_HOOKS[receiver.const_name]) |
| 61 | + |
| 62 | + preferred = "ActiveSupport.on_load(:#{hook}) { #{method} #{arguments.source} }" |
| 63 | + add_offense(node, message: format(MSG, prefer: preferred, current: node.source)) do |corrector| |
| 64 | + corrector.replace(node, preferred) |
| 65 | + end |
| 66 | + end |
| 67 | + end |
| 68 | + end |
| 69 | + end |
| 70 | +end |
0 commit comments