src/derails/passive_resistance/dependencies.rb
# frozen_string_literal: true

require "passive_resistance/dependencies/interlock"

module PassiveResistance # :nodoc:
  module Dependencies # :nodoc:
    require_relative "dependencies/require_dependency"

    singleton_class.attr_accessor :interlock
    @interlock = Interlock.new

    # :doc:

    # Execute the supplied block without interference from any
    # concurrent loads.
    def self.run_interlock(&block)
      interlock.running(&block)
    end

    # Execute the supplied block while holding an exclusive lock,
    # preventing any other thread from being inside a #run_interlock
    # block at the same time.
    def self.load_interlock(&block)
      interlock.loading(&block)
    end

    # Execute the supplied block while holding an exclusive lock,
    # preventing any other thread from being inside a #run_interlock
    # block at the same time.
    def self.unload_interlock(&block)
      interlock.unloading(&block)
    end

    # :nodoc:

    # The array of directories from which we autoload and reload, if reloading
    # is enabled. The public interface to push directories to this collection
    # from applications or engines is config.autoload_paths.
    #
    # This collection is allowed to have intersection with autoload_once_paths.
    # Common directories are not reloaded.
    singleton_class.attr_accessor :autoload_paths
    self.autoload_paths = []

    # The array of directories from which we autoload and never reload, even if
    # reloading is enabled. The public interface to push directories to this
    # collection from applications or engines is config.autoload_once_paths.
    singleton_class.attr_accessor :autoload_once_paths
    self.autoload_once_paths = []

    # This is a private set that collects all eager load paths during bootstrap.
    # Useful for Zeitwerk integration. The public interface to push custom
    # directories to this collection from applications or engines is
    # config.eager_load_paths.
    singleton_class.attr_accessor :_eager_load_paths
    self._eager_load_paths = Set.new

    # If reloading is enabled, this private set holds autoloaded classes tracked
    # by the descendants tracker. It is populated by an on_load callback in the
    # main autoloader. Used to clear state.
    singleton_class.attr_accessor :_autoloaded_tracked_classes
    self._autoloaded_tracked_classes = Set.new

    # If reloading is enabled, this private attribute stores the main autoloader
    # of a Rails application. It is `nil` otherwise.
    #
    # The public interface for this autoloader is `Rails.autoloaders.main`.
    singleton_class.attr_accessor :autoloader

    # Private method that reloads constants autoloaded by the main autoloader.
    #
    # Rails.application.reloader.reload! is the public interface for application
    # reload. That involves more things, like deleting unloaded classes from the
    # internal state of the descendants tracker, or reloading routes.
    def self.clear
      unload_interlock do
        _autoloaded_tracked_classes.clear
        autoloader.reload
      end
    end

    # Private method used by require_dependency.
    def self.search_for_file(relpath)
      relpath += ".rb" unless relpath.end_with?(".rb")
      autoload_paths.each do |autoload_path|
        abspath = File.join(autoload_path, relpath)
        return abspath if File.file?(abspath)
      end
      nil
    end

    # Private method that helps configuring the autoloaders.
    def self.eager_load?(path)
      _eager_load_paths.member?(path)
    end
  end
end
