_readNodeModulesList(nodeModulesDir) {
    let normalizedNodeModulesDir = path.normalize(nodeModulesDir);

    // Much of the time, normalizedNodeModulesDir is already a real path (i.e.
    // fs.realpathSync will return the same value as normalizedNodeModulesDir, if
    // the directory actually exists). Because of that, we'll assume
    // we can test for normalizedNodeModulesDir first and return if we find it.
    let nodeModulesList = this.getEntry(normalizedNodeModulesDir);
    if (nodeModulesList) {
      return nodeModulesList;
    }

    // NOTE: because we call this when searching for objects in node_modules
    // directories that may not exist, we'll just return null here if the
    // directory is not real. If it actually is an error in some case,
    // the caller can create the error there.
    let realPath = getRealDirectoryPath(normalizedNodeModulesDir);

    if (!realPath) {
      return null;
    }

    // realPath may be different than the original normalizedNodeModulesDir, so
    // we need to check the cache again.
    if (realPath !== normalizedNodeModulesDir) {
      nodeModulesList = this.getEntry(realPath);
      if (nodeModulesList) {
        return nodeModulesList;
      }
    } else {
      // getRealDirectoryPath is same as normalizedPackageDir, and we know already we
      // don't have an entry there, so we need to create one.
    }

    // At this point we know the directory node_modules exists and we can
    // process it. Further errors will be recorded here, or in the objects
    // that correspond to the node_modules entries.
    logger.info('Creating new NodeModulesList instance for %o', realPath);
    nodeModulesList = new NodeModulesList(realPath, this);

    const entries = fs.readdirSync(realPath).filter((fileName) => {
      if (fileName.startsWith('.') || fileName.startsWith('_')) {
        // we explicitly want to ignore these, according to the
        // definition of a valid package name.
        return false;
      } else if (fileName.startsWith('@')) {
        return true;
      } else if (!fs.existsSync(`${realPath}/${fileName}/package.json`)) {
        // a node_module is only valid if it contains a package.json
        return false;
      } else {
        return true;
      }
    }); // should not fail because getRealDirectoryPath passed

    entries.forEach((entryName) => {
      // entries should be either a package or a scoping directory. I think
      // there can also be files, but we'll ignore those.

      let entryPath = path.join(realPath, entryName);

      if (getRealFilePath(entryPath)) {
        // we explicitly want to ignore valid regular files in node_modules.
        // This is a bit slower than just checking for directories, but we need to be sure.
        return;
      }

      // At this point we have an entry name that should correspond to
      // a directory, which should turn into either a NodeModulesList or
      // PackageInfo. If not, it's an error on this NodeModulesList.
      let entryVal;

      if (entryName.startsWith('@')) {
        // we should have a scoping directory.
        entryVal = this._readNodeModulesList(entryPath);

        // readModulesDir only returns NodeModulesList or null
        if (entryVal instanceof NodeModulesList) {
          nodeModulesList.addEntry(entryName, entryVal);
        } else {
          // This (null return) really should not occur, unless somehow the
          // dir disappears between the time of fs.readdirSync and now.
          nodeModulesList.addError(Errors.ERROR_NODEMODULES_ENTRY_MISSING, entryName);
        }
      } else {
        // we should have a package. We will always get a PackageInfo
        // back, though it may contain errors.
        entryVal = this._readPackage(entryPath);
        nodeModulesList.addEntry(entryName, entryVal);
      }
    });

    this._addEntry(realPath, nodeModulesList);

    return nodeModulesList;
  }
Process a directory of modules in a given package directory.

We will allow cache entries for node_modules that actually
have no contents, just so we don't have to hit the file system more
often than necessary--it's much quicker to check an in-memory object.
object.

Note: only a NodeModulesList or null is returned.

@private
@method _readModulesList
@param {String} nodeModulesDir the path of the node_modules directory
 to read the package.json from and process the contents and create a
 new cache entry or entries.