Repository: webpack/enhanced-resolve
Pull request #326 (2022-02-04 20:51:57)

add yield in resolve context
- If `resolveContext.yield` presented will return resolve results there `resolveContext.yield(obj)`.
- In this case resolve callback will not return any result.
- If no result yielded error with log will be returned as always.



codecov[bot]: # [Codecov](https://codecov.io/gh/webpack/enhanced-resolve/pull/326?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=webpack) Report
> Merging [#326](https://codecov.io/gh/webpack/enhanced-resolve/pull/326?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=webpack) (80526fe) into [main](https://codecov.io/gh/webpack/enhanced-resolve/commit/d1a3cb65cdf90754871a263172006e2902a2f86e?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=webpack) (d1a3cb6) will **increase** coverage by `0.04%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/webpack/enhanced-resolve/pull/326/graphs/tree.svg?width=650&height=150&src=pr&token=tgdM0Gr6Ub&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=webpack)](https://codecov.io/gh/webpack/enhanced-resolve/pull/326?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=webpack)

```diff
@@            Coverage Diff             @@
##             main     #326      +/-   ##
==========================================
+ Coverage   94.93%   94.97%   +0.04%     
==========================================
  Files          39       39              
  Lines        1580     1593      +13     
==========================================
+ Hits         1500     1513      +13     
  Misses         80       80              
```


| [Impacted Files](https://codecov.io/gh/webpack/enhanced-resolve/pull/326?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=webpack) | Coverage Δ | |
|---|---|---|
| [lib/createInnerContext.js](https://codecov.io/gh/webpack/enhanced-resolve/pull/326/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=webpack#diff-bGliL2NyZWF0ZUlubmVyQ29udGV4dC5qcw==) | `100.00% <ø> (ø)` | |
| [lib/Resolver.js](https://codecov.io/gh/webpack/enhanced-resolve/pull/326/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=webpack#diff-bGliL1Jlc29sdmVyLmpz) | `87.70% <100.00%> (+0.97%)` | :arrow_up: |
| [lib/ResultPlugin.js](https://codecov.io/gh/webpack/enhanced-resolve/pull/326/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=webpack#diff-bGliL1Jlc3VsdFBsdWdpbi5qcw==) | `100.00% <100.00%> (ø)` | |
| [lib/getPaths.js](https://codecov.io/gh/webpack/enhanced-resolve/pull/326/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=webpack#diff-bGliL2dldFBhdGhzLmpz) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/webpack/enhanced-resolve/pull/326?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=webpack).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=webpack)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/webpack/enhanced-resolve/pull/326?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=webpack). Last update [d1a3cb6...80526fe](https://codecov.io/gh/webpack/enhanced-resolve/pull/326?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=webpack). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=webpack).


Patch:
diff --git a/lib/Resolver.js b/lib/Resolver.js
index a312008..fcdbc0d 100644
--- a/lib/Resolver.js
+++ b/lib/Resolver.js
@@ -104,6 +104,7 @@ const {
  * @property {WriteOnlySet<string>=} missingDependencies dependencies that was not found on file system
  * @property {Set<StackEntry>=} stack set of hooks' calls. For instance, `resolve → parsedResolve → describedResolve`,
  * @property {(function(string): void)=} log log function
+ * @property {(function (ResolveRequest): void)=} yield yield result, if provided plugins can return several results
  */
 
 /** @typedef {AsyncSeriesBailHook<[ResolveRequest, ResolveContext], ResolveRequest | null>} ResolveStepHook */
@@ -272,6 +273,16 @@ class Resolver {
 			request: request
 		};
 
+		let yield_;
+		let yieldCalled = false;
+		if (typeof resolveContext.yield === "function") {
+			const old = resolveContext.yield;
+			yield_ = obj => {
+				yieldCalled = true;
+				old(obj);
+			};
+		}
+
 		const message = `resolve '${request}' in '${path}'`;
 
 		const finishResolved = result => {
@@ -309,6 +320,7 @@ class Resolver {
 						parentLog(msg);
 						log.push(msg);
 					},
+					yield: yield_,
 					fileDependencies: resolveContext.fileDependencies,
 					contextDependencies: resolveContext.contextDependencies,
 					missingDependencies: resolveContext.missingDependencies,
@@ -318,6 +330,7 @@ class Resolver {
 					if (err) return callback(err);
 
 					if (result) return finishResolved(result);
+					if (yieldCalled) return callback(null);
 
 					return finishWithoutResolve(log);
 				}
@@ -331,6 +344,7 @@ class Resolver {
 				message,
 				{
 					log: undefined,
+					yield: yield_,
 					fileDependencies: resolveContext.fileDependencies,
 					contextDependencies: resolveContext.contextDependencies,
 					missingDependencies: resolveContext.missingDependencies,
@@ -340,6 +354,7 @@ class Resolver {
 					if (err) return callback(err);
 
 					if (result) return finishResolved(result);
+					if (yieldCalled) return callback(null);
 
 					// log is missing for the error details
 					// so we redo the resolving for the log info
@@ -354,11 +369,15 @@ class Resolver {
 						message,
 						{
 							log: msg => log.push(msg),
+							yield: yield_,
 							stack: resolveContext.stack
 						},
-						(err, result) => {
+						err => {
 							if (err) return callback(err);
 
+							// In a case that there is a race condition and yield will be called
+							if (yieldCalled) return callback(null);
+
 							return finishWithoutResolve(log);
 						}
 					);
@@ -397,6 +416,7 @@ class Resolver {
 			const innerContext = createInnerContext(
 				{
 					log: resolveContext.log,
+					yield: resolveContext.yield,
 					fileDependencies: resolveContext.fileDependencies,
 					contextDependencies: resolveContext.contextDependencies,
 					missingDependencies: resolveContext.missingDependencies,
diff --git a/lib/ResultPlugin.js b/lib/ResultPlugin.js
index b849694..e25c43f 100644
--- a/lib/ResultPlugin.js
+++ b/lib/ResultPlugin.js
@@ -29,7 +29,12 @@ module.exports = class ResultPlugin {
 					resolverContext.log("reporting result " + obj.path);
 				resolver.hooks.result.callAsync(obj, resolverContext, err => {
 					if (err) return callback(err);
-					callback(null, obj);
+					if (typeof resolverContext.yield === "function") {
+						resolverContext.yield(obj);
+						callback(null, null);
+					} else {
+						callback(null, obj);
+					}
 				});
 			}
 		);
diff --git a/lib/createInnerContext.js b/lib/createInnerContext.js
index 1cbd86f..0529356 100644
--- a/lib/createInnerContext.js
+++ b/lib/createInnerContext.js
@@ -27,6 +27,7 @@ module.exports = function createInnerContext(
 	}
 	const childContext = {
 		log: innerLog,
+		yield: options.yield,
 		fileDependencies: options.fileDependencies,
 		contextDependencies: options.contextDependencies,
 		missingDependencies: options.missingDependencies,
diff --git a/lib/getPaths.js b/lib/getPaths.js
index 470fc6f..65858f4 100644
--- a/lib/getPaths.js
+++ b/lib/getPaths.js
@@ -6,6 +6,7 @@
 "use strict";
 
 module.exports = function getPaths(path) {
+	if (path === "/") return { paths: ["/"], seqments: [""] };
 	const parts = path.split(/(.*?[\\/]+)/);
 	const paths = [path];
 	const seqments = [parts[parts.length - 1]];
diff --git a/types.d.ts b/types.d.ts
index f392bd1..f2937d1 100644
--- a/types.d.ts
+++ b/types.d.ts
@@ -204,6 +204,11 @@ declare interface ResolveContext {
 	 * log function
 	 */
 	log?: (arg0: string) => void;
+
+	/**
+	 * yield result, if provided plugins can return several results
+	 */
+	yield?: (arg0: ResolveRequest) => void;
 }
 declare interface ResolveOptions {
 	alias: AliasOption[];


Test patch:
diff --git a/test/getPaths.js b/test/getPaths.js
new file mode 100644
index 0000000..8c72076
--- /dev/null
+++ b/test/getPaths.js
@@ -0,0 +1,25 @@
+require("should");
+
+const getPaths = require("../lib/getPaths");
+
+/**
+ * @type {[string,{paths: string[], seqments: string[]}][]}
+ */
+const cases = [
+	["/a", { paths: ["/a", "/"], seqments: ["a", "/"] }],
+	["/a/", { paths: ["/a/", "/a", "/"], seqments: ["", "a", "/"] }],
+	["/a/b", { paths: ["/a/b", "/a", "/"], seqments: ["b", "a", "/"] }],
+	[
+		"/a/b/",
+		{ paths: ["/a/b/", "/a/b", "/a", "/"], seqments: ["", "b", "a", "/"] }
+	],
+	["/", { paths: ["/"], seqments: [""] }]
+];
+
+cases.forEach(case_ => {
+	it(case_[0], () => {
+		const { paths, seqments } = getPaths(case_[0]);
+		paths.should.be.eql(case_[1].paths);
+		seqments.should.be.eql(case_[1].seqments);
+	});
+});
diff --git a/test/yield.js b/test/yield.js
new file mode 100644
index 0000000..dc13d20
--- /dev/null
+++ b/test/yield.js
@@ -0,0 +1,169 @@
+const should = require("should");
+
+const { ResolverFactory } = require("../");
+const { Volume } = require("memfs");
+
+/** @typedef {import("../lib/Resolver").ResolveContext} ResolveContext */
+/** @typedef {ResolveContext & Required<Pick<ResolveContext, 'yield' | 'fileDependencies' | 'contextDependencies' | 'missingDependencies'>>} StrictResolveContext */
+
+const fileSystem = Volume.fromJSON(
+	{
+		"/a/foo/a": "",
+		"/a/foo/b": "",
+		"/a/foo-2/b": "",
+		"/a/foo-2/c": "",
+		"/b/foo/a": ""
+	},
+	"/"
+);
+
+describe("should resolve all aliases", () => {
+	const resolver = ResolverFactory.createResolver({
+		extensions: [".js"],
+		alias: {
+			index: ["/a/foo", "/a/foo-2"]
+		},
+		modules: "/",
+		fileSystem
+	});
+	const modulesResolver = ResolverFactory.createResolver({
+		extensions: [".js"],
+		modules: ["a", "b"],
+		fileSystem
+	});
+
+	it("should yield all b files", done => {
+		const paths = [];
+		const yield_ = ({ path }) => paths.push(path);
+		const fileDependencies = new Set();
+		const contextDependencies = new Set();
+		const missingDependencies = new Set();
+		/** @type {ResolveContext} */
+		const context = {
+			yield: yield_,
+			fileDependencies,
+			contextDependencies,
+			missingDependencies
+		};
+
+		resolver.resolve({}, "/", "index/b", context, (err, result) => {
+			should(err).be.eql(null);
+			should(result).be.eql(undefined);
+			should(paths).be.eql(["/a/foo/b", "/a/foo-2/b"]);
+			should(Array.from(fileDependencies.values()).sort()).be.eql([
+				"/",
+				"/a",
+				"/a/foo",
+				"/a/foo-2",
+				"/a/foo-2/b",
+				"/a/foo/b"
+			]);
+			should(Array.from(missingDependencies.values()).sort()).be.eql([
+				"/a/foo-2/b",
+				"/a/foo-2/b.js",
+				"/a/foo-2/package.json",
+				"/a/foo/b",
+				"/a/foo/b.js",
+				"/a/foo/package.json",
+				"/a/package.json",
+				"/package.json"
+			]);
+			should(Array.from(contextDependencies.values()).sort()).be.eql([]);
+			done();
+		});
+	});
+
+	it("should yield all foo files", done => {
+		const paths = [];
+		const yield_ = ({ path }) => paths.push(path);
+		const fileDependencies = new Set();
+		const contextDependencies = new Set();
+		const missingDependencies = new Set();
+		/** @type {ResolveContext} */
+		const context = {
+			yield: yield_,
+			fileDependencies,
+			contextDependencies,
+			missingDependencies
+		};
+
+		modulesResolver.resolve({}, "/", "foo/a", context, (err, result) => {
+			should(err).be.eql(null);
+			should(result).be.eql(undefined);
+			should(paths).be.eql(["/a/foo/a", "/b/foo/a"]);
+			should(Array.from(fileDependencies.values()).sort()).be.eql([
+				"/",
+				"/a",
+				"/a/foo",
+				"/a/foo/a",
+				"/b",
+				"/b/foo",
+				"/b/foo/a"
+			]);
+			should(Array.from(missingDependencies.values()).sort()).be.eql([
+				"/a/foo/a",
+				"/a/foo/a.js",
+				"/a/foo/package.json",
+				"/a/package.json",
+				"/b/foo/a",
+				"/b/foo/a.js",
+				"/b/foo/package.json",
+				"/b/package.json",
+				"/package.json"
+			]);
+			should(Array.from(contextDependencies.values()).sort()).be.eql([]);
+			done();
+		});
+	});
+
+	it("should yield c file", done => {
+		const paths = [];
+		const yield_ = ({ path }) => paths.push(path);
+		/** @type {ResolveContext} */
+		const context = {
+			yield: yield_
+		};
+
+		resolver.resolve({}, "/", "index/c", context, (err, result) => {
+			should(err).be.eql(null);
+			should(result).be.eql(undefined);
+			should(paths).be.eql(["/a/foo-2/c"]);
+			done();
+		});
+	});
+
+	it("should return error if no resolve", done => {
+		const paths = [];
+		const yield_ = ({ path }) => paths.push(path);
+		const fileDependencies = new Set();
+		const contextDependencies = new Set();
+		const missingDependencies = new Set();
+		/** @type {ResolveContext} */
+		const context = {
+			yield: yield_,
+			fileDependencies,
+			contextDependencies,
+			missingDependencies
+		};
+
+		resolver.resolve({}, "/", "index/unknown", context, (err, result) => {
+			should(err).not.be.eql(null);
+			should(err).not.be.eql(undefined);
+			should(result).be.eql(undefined);
+			should(paths).be.eql([]);
+			should(Array.from(fileDependencies.values()).sort()).be.eql([]);
+			should(Array.from(missingDependencies.values()).sort()).be.eql([
+				"/a/foo-2/package.json",
+				"/a/foo-2/unknown",
+				"/a/foo-2/unknown.js",
+				"/a/foo/package.json",
+				"/a/foo/unknown",
+				"/a/foo/unknown.js",
+				"/a/package.json",
+				"/package.json"
+			]);
+			should(Array.from(contextDependencies.values()).sort()).be.eql([]);
+			done();
+		});
+	});
+});