_readContext(
		{
			path,
			fromImmutablePath,
			fromManagedItem,
			fromSymlink,
			fromFile,
			fromDirectory,
			reduce
		},
		callback
	) {
		this.fs.readdir(path, (err, _files) => {
			if (err) {
				if (err.code === "ENOENT") {
					return callback(null, null);
				}
				return callback(err);
			}
			const files = /** @type {string[]} */ (_files)
				.map(file => file.normalize("NFC"))
				.filter(file => !/^\./.test(file))
				.sort();
			asyncLib.map(
				files,
				(file, callback) => {
					const child = join(this.fs, path, file);
					for (const immutablePath of this.immutablePathsRegExps) {
						if (immutablePath.test(path)) {
							// ignore any immutable path for timestamping
							return callback(null, fromImmutablePath(path));
						}
					}
					for (const immutablePath of this.immutablePathsWithSlash) {
						if (path.startsWith(immutablePath)) {
							// ignore any immutable path for timestamping
							return callback(null, fromImmutablePath(path));
						}
					}
					for (const managedPath of this.managedPathsRegExps) {
						const match = managedPath.exec(path);
						if (match) {
							const managedItem = getManagedItem(match[1], path);
							if (managedItem) {
								// construct timestampHash from managed info
								return this.managedItemQueue.add(managedItem, (err, info) => {
									if (err) return callback(err);
									return callback(
										null,
										fromManagedItem(/** @type {string} */ (info))
									);
								});
							}
						}
					}
					for (const managedPath of this.managedPathsWithSlash) {
						if (path.startsWith(managedPath)) {
							const managedItem = getManagedItem(managedPath, child);
							if (managedItem) {
								// construct timestampHash from managed info
								return this.managedItemQueue.add(managedItem, (err, info) => {
									if (err) return callback(err);
									return callback(
										null,
										fromManagedItem(/** @type {string} */ (info))
									);
								});
							}
						}
					}

					lstatReadlinkAbsolute(this.fs, child, (err, _stat) => {
						if (err) return callback(err);

						const stat = /** @type {IStats | string} */ (_stat);

						if (typeof stat === "string") {
							return fromSymlink(child, stat, callback);
						}

						if (stat.isFile()) {
							return fromFile(child, stat, callback);
						}
						if (stat.isDirectory()) {
							return fromDirectory(child, stat, callback);
						}
						callback(null, null);
					});
				},
				(err, results) => {
					if (err) return callback(err);
					const result = reduce(files, /** @type {ItemType[]} */ (results));
					callback(null, result);
				}
			);
		});
	}
@private
@template T
@template ItemType
@param {object} options options
@param {string} options.path path
@param {(value: string) => ItemType} options.fromImmutablePath called when context item is an immutable path
@param {(value: string) => ItemType} options.fromManagedItem called when context item is a managed path
@param {(value: string, result: string, callback: (err?: WebpackError | null, itemType?: ItemType) => void) => void} options.fromSymlink called when context item is a symlink
@param {(value: string, stats: IStats, callback: (err?: WebpackError | null, itemType?: ItemType | null) => void) => void} options.fromFile called when context item is a file
@param {(value: string, stats: IStats, callback: (err?: WebpackError | null, itemType?: ItemType) => void) => void} options.fromDirectory called when context item is a directory
@param {(arr: string[], arr1: ItemType[]) => T} options.reduce called from all context items
@param {(err?: Error | null, result?: T | null) => void} callback callback