makeImportHookMaker = (
  readPowers,
  baseLocation,
  {
    sources = create(null),
    compartmentDescriptors = create(null),
    archiveOnly = false,
    computeSha512 = undefined,
    searchSuffixes = nodejsConventionSearchSuffixes,
    sourceMapHook = undefined,
    entryCompartmentName,
    entryModuleSpecifier,
    importHook: exitModuleImportHook = undefined,
  },
) => {
  // Set of specifiers for modules (scoped to compartment) whose parser is not
  // using heuristics to determine imports.
  /** @type {Map<string, Set<string>>} compartment name ->* module specifier */
  const strictlyRequired = new Map([
    [entryCompartmentName, new Set([entryModuleSpecifier])],
  ]);

  /**
   * @param {string} compartmentName
   */
  const strictlyRequiredForCompartment = compartmentName => {
    let compartmentStrictlyRequired = strictlyRequired.get(compartmentName);
    if (compartmentStrictlyRequired !== undefined) {
      return compartmentStrictlyRequired;
    }
    compartmentStrictlyRequired = new Set();
    strictlyRequired.set(compartmentName, compartmentStrictlyRequired);
    return compartmentStrictlyRequired;
  };

  // per-compartment:
  /** @type {ImportHookMaker} */
  const makeImportHook = ({
    packageLocation,
    packageName: _packageName,
    attenuators,
    parse,
    shouldDeferError,
    compartments,
  }) => {
    // per-compartment:
    packageLocation = resolveLocation(packageLocation, baseLocation);
    const packageSources = sources[packageLocation] || create(null);
    sources[packageLocation] = packageSources;
    const compartmentDescriptor = compartmentDescriptors[packageLocation] || {};
    const { modules: moduleDescriptors = create(null) } = compartmentDescriptor;
    compartmentDescriptor.modules = moduleDescriptors;

    const deferError = makeDeferError(
      strictlyRequiredForCompartment,
      packageLocation,
      packageSources,
    );

    /** @type {ImportHook} */
    const importHook = async moduleSpecifier => {
      compartmentDescriptor.retained = true;

      // for lint rule
      await null;

      // All importHook errors must be deferred if coming from loading dependencies
      // identified by a parser that discovers imports heuristically.
      try {
        // per-module:

        // In Node.js, an absolute specifier always indicates a built-in or
        // third-party dependency.
        // The `moduleMapHook` captures all third-party dependencies, unless
        // we allow importing any exit.
        if (moduleSpecifier !== '.' && !moduleSpecifier.startsWith('./')) {
          if (exitModuleImportHook) {
            const record = await exitModuleImportHook(
              moduleSpecifier,
              packageLocation,
            );
            if (record) {
              // It'd be nice to check the policy before importing it, but we can only throw a policy error if the
              // hook returns something. Otherwise, we need to fall back to the 'cannot find' error below.
              enforceModulePolicy(moduleSpecifier, compartmentDescriptor, {
                exit: true,
                errorHint: `Blocked in loading. ${q(
                  moduleSpecifier,
                )} was not in the compartment map and an attempt was made to load it as a builtin`,
              });
              if (archiveOnly) {
                // Return a place-holder.
                // Archived compartments are not executed.
                return freeze({ imports: [], exports: [], execute() {} });
              }
              // note it's not being marked as exit in sources
              // it could get marked and the second pass, when the archive is being executed, would have the data
              // to enforce which exits can be dynamically imported
              const attenuatedRecord = await attenuateModuleHook(
                moduleSpecifier,
                record,
                compartmentDescriptor.policy,
                attenuators,
              );
              return attenuatedRecord;
            }
          }
          throw Error(
            `Cannot find external module ${q(
              moduleSpecifier,
            )} in package ${packageLocation}`,
          );
        }

        const { maybeRead } = unpackReadPowers(readPowers);

        const candidates = nominateCandidates(moduleSpecifier, searchSuffixes);

        const record = await asyncTrampoline(
          chooseModuleDescriptor,
          {
            candidates,
            compartmentDescriptor,
            compartmentDescriptors,
            compartments,
            computeSha512,
            moduleDescriptors,
            moduleSpecifier,
            packageLocation,
            packageSources,
            readPowers,
            archiveOnly,
            sourceMapHook,
            strictlyRequiredForCompartment,
          },
          { maybeRead, parse, shouldDeferError },
        );

        if (record) {
          return record;
        }

        throw Error(
          `Cannot find file for internal module ${q(
            moduleSpecifier,
          )} (with candidates ${candidates
            .map(x => q(x))
            .join(', ')}) in package ${packageLocation}`,
        );
      } catch (error) {
        return deferError(moduleSpecifier, error);
      }
    };
    return importHook;
  };
  return makeImportHook;
}
@param {ReadFn|ReadPowers} readPowers
@param {string} baseLocation
@param {MakeImportHookMakerOptions} options
@returns {ImportHookMaker}