makeModuleMapHook = (
  compartmentDescriptor,
  compartments,
  compartmentName,
  moduleDescriptors,
  scopeDescriptors,
) => {
  /**
   * @param {string} moduleSpecifier
   * @returns {string | object | undefined}
   */
  const moduleMapHook = moduleSpecifier => {
    compartmentDescriptor.retained = true;

    const moduleDescriptor = moduleDescriptors[moduleSpecifier];
    if (moduleDescriptor !== undefined) {
      moduleDescriptor.retained = true;

      // "foreignCompartmentName" refers to the compartment which
      // may differ from the current compartment
      const {
        compartment: foreignCompartmentName = compartmentName,
        module: foreignModuleSpecifier,
        exit,
      } = moduleDescriptor;
      if (exit !== undefined) {
        return undefined; // fall through to import hook
      }
      if (foreignModuleSpecifier !== undefined) {
        // archive goes through foreignModuleSpecifier for local modules too
        if (!moduleSpecifier.startsWith('./')) {
          // This code path seems to only be reached on subsequent imports of the same specifier in the same compartment.
          // The check should be redundant and is only left here out of abundance of caution.
          enforceModulePolicy(moduleSpecifier, compartmentDescriptor, {
            exit: false,
            errorHint:
              'This check should not be reachable. If you see this error, please file an issue.',
          });
        }

        const foreignCompartment = compartments[foreignCompartmentName];
        if (foreignCompartment === undefined) {
          throw Error(
            `Cannot import from missing compartment ${q(
              foreignCompartmentName,
            )}}`,
          );
        }
        return {
          compartment: foreignCompartment,
          namespace: foreignModuleSpecifier,
        };
      }
    }

    // Search for a scope that shares a prefix with the requested module
    // specifier.
    // This might be better with a trie, but only a benchmark on real-world
    // data would tell us whether the additional complexity would translate to
    // better performance, so this is left readable and presumed slow for now.
    for (const [scopePrefix, scopeDescriptor] of entries(scopeDescriptors)) {
      const foreignModuleSpecifier = trimModuleSpecifierPrefix(
        moduleSpecifier,
        scopePrefix,
      );

      if (foreignModuleSpecifier !== undefined) {
        const { compartment: foreignCompartmentName } = scopeDescriptor;
        if (foreignCompartmentName === undefined) {
          throw Error(
            `Cannot import from scope ${scopePrefix} due to missing "compartment" property`,
          );
        }
        const foreignCompartment = compartments[foreignCompartmentName];
        if (foreignCompartment === undefined) {
          throw Error(
            `Cannot import from missing compartment ${q(
              foreignCompartmentName,
            )}`,
          );
        }

        enforceModulePolicy(scopePrefix, compartmentDescriptor, {
          exit: false,
          errorHint: `Blocked in linking. ${q(
            moduleSpecifier,
          )} is part of the compartment map and resolves to ${q(
            foreignCompartmentName,
          )}.`,
        });
        // The following line is weird.
        // Information is flowing backward.
        // This moduleMapHook writes back to the `modules` descriptor, from the
        // original compartment map.
        // So the compartment map that was used to create the compartment
        // assembly, can then be captured in an archive, obviating the need for
        // a moduleMapHook when we assemble compartments from the resulting
        // archive.
        moduleDescriptors[moduleSpecifier] = {
          retained: true,
          compartment: foreignCompartmentName,
          module: foreignModuleSpecifier,
        };
        return {
          compartment: foreignCompartment,
          namespace: foreignModuleSpecifier,
        };
      }
    }

    // No entry in the module map.
    // Compartments will fall through to their `importHook`.
    return undefined;
  };

  return moduleMapHook;
}
`makeModuleMapHook` generates a `moduleMapHook` for the `Compartment`
constructor, suitable for Node.js style packages where any module in the
package might be imported.
Since searching for all of these modules up front is either needlessly
costly (on a file system) or impossible (from a web service), we
let the import graph guide our search.
Any module specifier with an absolute prefix should be captured by
the `moduleMap` or `moduleMapHook`.

@param {CompartmentDescriptor} compartmentDescriptor
@param {Record<string, Compartment>} compartments
@param {string} compartmentName
@param {Record<string, ModuleDescriptor>} moduleDescriptors
@param {Record<string, ModuleDescriptor>} scopeDescriptors
@returns {ModuleMapHook | undefined}