Merge pull request #12631 from webpack/feature/resolve-hint

add some better hints when resolving fails
This commit is contained in:
Tobias Koppers 2021-02-09 12:25:37 +01:00 committed by GitHub
commit 4ab27ff723
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 146 additions and 23 deletions

View File

@ -750,35 +750,148 @@ class NormalModuleFactory extends ModuleFactory {
resolveContext, resolveContext,
(err, resolvedResource, resolvedResourceResolveData) => { (err, resolvedResource, resolvedResourceResolveData) => {
if (err) { if (err) {
if (resolver.options.fullySpecified) { return this._resolveResourceErrorHints(
resolver err,
.withOptions({ contextInfo,
fullySpecified: false context,
}) unresolvedResource,
.resolve( resolver,
contextInfo, resolveContext,
context, (err2, hints) => {
unresolvedResource, if (err2) {
resolveContext, err.message += `
(err2, resolvedResource) => { An fatal error happened during resolving additional hints for this error: ${err2.message}`;
if (!err2 && resolvedResource) { err.stack += `
const resource = parseResource(
resolvedResource An fatal error happened during resolving additional hints for this error:
).path.replace(/^.*[\\/]/, ""); ${err2.stack}`;
err.message += ` return callback(err);
Did you mean '${resource}'? }
if (hints && hints.length > 0) {
err.message += `
${hints.join("\n\n")}`;
}
callback(err);
}
);
}
callback(err, resolvedResource, resolvedResourceResolveData);
}
);
}
_resolveResourceErrorHints(
error,
contextInfo,
context,
unresolvedResource,
resolver,
resolveContext,
callback
) {
asyncLib.parallel(
[
callback => {
if (!resolver.options.fullySpecified) return callback();
resolver
.withOptions({
fullySpecified: false
})
.resolve(
contextInfo,
context,
unresolvedResource,
resolveContext,
(err, resolvedResource) => {
if (!err && resolvedResource) {
const resource = parseResource(resolvedResource).path.replace(
/^.*[\\/]/,
""
);
return callback(
null,
`Did you mean '${resource}'?
BREAKING CHANGE: The request '${unresolvedResource}' failed to resolve only because it was resolved as fully specified BREAKING CHANGE: The request '${unresolvedResource}' failed to resolve only because it was resolved as fully specified
(probably because the origin is a '*.mjs' file or a '*.js' file where the package.json contains '"type": "module"'). (probably because the origin is a '*.mjs' file or a '*.js' file where the package.json contains '"type": "module"').
The extension in the request is mandatory for it to be fully specified. The extension in the request is mandatory for it to be fully specified.
Add the extension to the request.`; Add the extension to the request.`
} );
callback(err);
} }
); callback();
return; }
);
},
callback => {
if (!resolver.options.enforceExtension) return callback();
resolver
.withOptions({
enforceExtension: false,
extensions: []
})
.resolve(
contextInfo,
context,
unresolvedResource,
resolveContext,
(err, resolvedResource) => {
if (!err && resolvedResource) {
let hint = "";
const match = /(\.[^.]+)(\?|$)/.exec(unresolvedResource);
if (match) {
const fixedRequest = unresolvedResource.replace(
/(\.[^.]+)(\?|$)/,
"$2"
);
if (resolver.options.extensions.has(match[1])) {
hint = `Did you mean '${fixedRequest}'?`;
} else {
hint = `Did you mean '${fixedRequest}'? Also note that '${match[1]}' is not in 'resolve.extensions' yet and need to be added for this to work?`;
}
} else {
hint = `Did you mean to omit the extension or to remove 'resolve.enforceExtension'?`;
}
return callback(
null,
`The request '${unresolvedResource}' failed to resolve only because 'resolve.enforceExtension' was specified.
${hint}
Including the extension in the request is no longer possible. Did you mean to enforce including the extension in requests with 'resolve.extensions: []' instead?`
);
}
callback();
}
);
},
callback => {
if (
/^\.\.?\//.test(unresolvedResource) ||
resolver.options.preferRelative
) {
return callback();
} }
resolver.resolve(
contextInfo,
context,
`./${unresolvedResource}`,
resolveContext,
(err, resolvedResource) => {
if (err || !resolvedResource) return callback();
const moduleDirectories = resolver.options.modules
.map(m => (Array.isArray(m) ? m.join(", ") : m))
.join(", ");
callback(
null,
`Did you mean './${unresolvedResource}'?
Requests that should resolve in the current directory need to start with './'.
Requests that start with a name are treated as module requests and resolve within module directories (${moduleDirectories}).
If changing the source code is not an option there is also a resolve options called 'preferRelative' which tries to resolve these kind of requests in the current directory too.`
);
}
);
} }
callback(err, resolvedResource, resolvedResourceResolveData); ],
(err, hints) => {
if (err) return callback(err);
callback(null, hints.filter(Boolean));
} }
); );
} }

View File

@ -0,0 +1 @@
export {};

View File

@ -0,0 +1,3 @@
module.exports = [
[/Can't resolve 'dependency\.js'/, /Did you mean '\.\/dependency\.js'\?/]
];

View File

@ -0,0 +1,5 @@
it("should not resolve module requests relative", async () => {
await expect(import("./module.mjs")).rejects.toMatchObject({
code: "MODULE_NOT_FOUND"
});
});

View File

@ -0,0 +1 @@
import "dependency.js";