From c286da01950aabb5718dcf617c561a7c93f92a76 Mon Sep 17 00:00:00 2001 From: Jean Lauliac Date: Thu, 11 May 2017 06:19:26 -0700 Subject: [PATCH] packager: ResolutionRequest: keep track of tentative resolution paths Summary: That's a bit of an experimental changeset, I wanted to experiment how we can track with more precision the resolution and this is what I come with. That allows us to know what has been tested if we canot find a match. It also uses a simpler control flow with early `return`s all across. Finally, this reflect the strategy I want to apply for the rest of the resolution. (Right now the error is still not great, as it may be deeply nested, as in the case of packages resolution.) Reviewed By: davidaurelio Differential Revision: D5044040 fbshipit-source-id: 2e256174506f80d90ee83175057666d530785788 --- .../DependencyGraph/ResolutionRequest.js | 147 +++++++++++++----- 1 file changed, 106 insertions(+), 41 deletions(-) diff --git a/packager/src/node-haste/DependencyGraph/ResolutionRequest.js b/packager/src/node-haste/DependencyGraph/ResolutionRequest.js index 8e7e0fb91..4e71580f3 100644 --- a/packager/src/node-haste/DependencyGraph/ResolutionRequest.js +++ b/packager/src/node-haste/DependencyGraph/ResolutionRequest.js @@ -110,6 +110,7 @@ function tryResolveSync(action: () => T, secondaryAction: () => T): T { } class ResolutionRequest { + _doesFileExist: (filePath: string) => boolean; _immediateResolutionCache: {[key: string]: TModule}; _options: Options; static emptyModule: string; @@ -117,6 +118,7 @@ class ResolutionRequest { constructor(options: Options) { this._options = options; this._resetResolutionCache(); + this._doesFileExist = filePath => this._options.hasteFS.exists(filePath); } _tryResolve( @@ -578,52 +580,32 @@ class ResolutionRequest { } _loadAsFile( - potentialModulePath: string, + basepath: string, fromModule: TModule, toModule: string, ): TModule { - if (this._options.helpers.isAssetFile(potentialModulePath)) { - return this._loadAsAssetFile(potentialModulePath, fromModule, toModule); + if (this._options.helpers.isAssetFile(basepath)) { + return this._loadAsAssetFile(basepath, fromModule, toModule); } - - let file; - if (this._options.hasteFS.exists(potentialModulePath)) { - file = potentialModulePath; - } else { - const {platform, preferNativePlatform, hasteFS} = this._options; - for (let i = 0; i < this._options.sourceExts.length; i++) { - const ext = this._options.sourceExts[i]; - if (platform != null) { - const platformSpecificPath = `${potentialModulePath}.${platform}.${ext}`; - if (hasteFS.exists(platformSpecificPath)) { - file = platformSpecificPath; - break; - } - } - if (preferNativePlatform) { - const nativeSpecificPath = `${potentialModulePath}.native.${ext}`; - if (hasteFS.exists(nativeSpecificPath)) { - file = nativeSpecificPath; - break; - } - } - const genericPath = `${potentialModulePath}.${ext}`; - if (hasteFS.exists(genericPath)) { - file = genericPath; - break; - } - } - - if (file == null) { - throw new UnableToResolveError( - fromModule, - toModule, - `File ${potentialModulePath} doesn't exist`, - ); - } + const dirPath = path.dirname(basepath); + const doesFileExist = this._doesFileExist; + const resolver = new FileNameResolver({doesFileExist, dirPath}); + const fileNamePrefix = path.basename(basepath); + const fileName = this._tryToResolveAllFileNames(resolver, fileNamePrefix); + if (fileName != null) { + return this._options.moduleCache.getModule(path.join(dirPath, fileName)); } - - return this._options.moduleCache.getModule(file); + throw new UnableToResolveError( + fromModule, + toModule, + `Could not resolve the base path \`${basepath}' into a module. The ` + + `folder \`${dirPath}' was searched for one of these files: ` + + resolver + .getTentativeFileNames() + .map(filePath => `\`${filePath}'`) + .join(', ') + + '.', + ); } _loadAsAssetFile( @@ -657,6 +639,60 @@ class ResolutionRequest { ); } + /** + * A particular 'base path' can resolve to a number of possibilities depending + * on the context. For example `foo/bar` could resolve to `foo/bar.ios.js`, or + * to `foo/bar.js`. If can also resolve to the bare path `foo/bar` itself, as + * supported by Node.js resolution. On the other hand it doesn't support + * `foo/bar.ios`, for historical reasons. + */ + _tryToResolveAllFileNames( + resolver: FileNameResolver, + fileNamePrefix: string, + ): ?string { + if (resolver.tryToResolveFileName(fileNamePrefix)) { + return fileNamePrefix; + } + const {sourceExts} = this._options; + for (let i = 0; i < sourceExts.length; i++) { + const fileName = this._tryToResolveFileNamesForExt( + fileNamePrefix, + resolver, + sourceExts[i], + ); + if (fileName != null) { + return fileName; + } + } + return null; + } + + /** + * For a particular extension, ex. `js`, we want to try a few possibilities, + * such as `foo.ios.js`, `foo.native.js`, and of course `foo.js`. + */ + _tryToResolveFileNamesForExt( + fileNamePrefix: string, + resolver: FileNameResolver, + ext: string, + ): ?string { + const {platform, preferNativePlatform} = this._options; + if (platform != null) { + const fileName = `${fileNamePrefix}.${platform}.${ext}`; + if (resolver.tryToResolveFileName(fileName)) { + return fileName; + } + } + if (preferNativePlatform) { + const fileName = `${fileNamePrefix}.native.${ext}`; + if (resolver.tryToResolveFileName(fileName)) { + return fileName; + } + } + const fileName = `${fileNamePrefix}.${ext}`; + return resolver.tryToResolveFileName(fileName) ? fileName : null; + } + _loadAsDir( potentialDirPath: string, fromModule: TModule, @@ -689,6 +725,35 @@ function resolutionHash(modulePath, depName) { return `${path.resolve(modulePath)}:${depName}`; } +type FileNameResolverOptions = {| + +dirPath: string, + +doesFileExist: (filePath: string) => boolean, +|}; + +/** + * When resolving a single module we want to keep track of the list of paths + * we tried to find. + */ +class FileNameResolver { + _options: FileNameResolverOptions; + _tentativeFileNames: Array; + + constructor(options: FileNameResolverOptions) { + this._options = options; + this._tentativeFileNames = []; + } + + getTentativeFileNames(): $ReadOnlyArray { + return this._tentativeFileNames; + } + + tryToResolveFileName(fileName: string): boolean { + this._tentativeFileNames.push(fileName); + const filePath = path.join(this._options.dirPath, fileName); + return this._options.doesFileExist(filePath); + } +} + class UnableToResolveError extends Error { type: string; from: string;