Handle packages inside another node modules package when auto importing (#37561)

Fixes #37542
This commit is contained in:
Sheetal Nandi 2020-03-25 16:27:02 -07:00 committed by GitHub
parent fd9e602fcf
commit 84a3252e76
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 82 additions and 27 deletions

View file

@ -319,33 +319,30 @@ namespace ts.moduleSpecifiers {
return undefined;
}
let packageJsonContent: any | undefined;
const packageRootPath = moduleFileName.substring(0, parts.packageRootIndex);
// Simplify the full file path to something that can be resolved by Node.
let moduleSpecifier = moduleFileName;
if (!packageNameOnly) {
const packageJsonPath = combinePaths(packageRootPath, "package.json");
packageJsonContent = host.fileExists(packageJsonPath)
? JSON.parse(host.readFile(packageJsonPath)!)
: undefined;
const versionPaths = packageJsonContent && packageJsonContent.typesVersions
? getPackageJsonTypesVersionsPaths(packageJsonContent.typesVersions)
: undefined;
if (versionPaths) {
const subModuleName = moduleFileName.slice(parts.packageRootIndex + 1);
const fromPaths = tryGetModuleNameFromPaths(
removeFileExtension(subModuleName),
removeExtensionAndIndexPostFix(subModuleName, Ending.Minimal, options),
versionPaths.paths
);
if (fromPaths !== undefined) {
moduleFileName = combinePaths(moduleFileName.slice(0, parts.packageRootIndex), fromPaths);
let packageRootIndex = parts.packageRootIndex;
let moduleFileNameForExtensionless: string | undefined;
while (true) {
// If the module could be imported by a directory name, use that directory's name
const { moduleFileToTry, packageRootPath } = tryDirectoryWithPackageJson(packageRootIndex);
if (packageRootPath) {
moduleSpecifier = packageRootPath;
break;
}
if (!moduleFileNameForExtensionless) moduleFileNameForExtensionless = moduleFileToTry;
// try with next level of directory
packageRootIndex = moduleFileName.indexOf(directorySeparator, packageRootIndex + 1);
if (packageRootIndex === -1) {
moduleSpecifier = getExtensionlessFileName(moduleFileNameForExtensionless);
break;
}
}
}
// Simplify the full file path to something that can be resolved by Node.
// If the module could be imported by a directory name, use that directory's name
const moduleSpecifier = packageNameOnly ? moduleFileName : getDirectoryOrExtensionlessFileName(moduleFileName);
const globalTypingsCacheLocation = host.getGlobalTypingsCacheLocation && host.getGlobalTypingsCacheLocation();
// Get a path that's relative to node_modules or the importing file's path
// if node_modules folder is in this folder or any of its parent folders, no need to keep it.
@ -360,18 +357,40 @@ namespace ts.moduleSpecifiers {
// For classic resolution, only allow importing from node_modules/@types, not other node_modules
return getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs && packageName === nodeModulesDirectoryName ? undefined : packageName;
function getDirectoryOrExtensionlessFileName(path: string): string {
// If the file is the main module, it can be imported by the package name
if (packageJsonContent) {
function tryDirectoryWithPackageJson(packageRootIndex: number) {
const packageRootPath = moduleFileName.substring(0, packageRootIndex);
const packageJsonPath = combinePaths(packageRootPath, "package.json");
let moduleFileToTry = moduleFileName;
if (host.fileExists(packageJsonPath)) {
const packageJsonContent = JSON.parse(host.readFile!(packageJsonPath)!);
const versionPaths = packageJsonContent.typesVersions
? getPackageJsonTypesVersionsPaths(packageJsonContent.typesVersions)
: undefined;
if (versionPaths) {
const subModuleName = moduleFileName.slice(packageRootPath.length + 1);
const fromPaths = tryGetModuleNameFromPaths(
removeFileExtension(subModuleName),
removeExtensionAndIndexPostFix(subModuleName, Ending.Minimal, options),
versionPaths.paths
);
if (fromPaths !== undefined) {
moduleFileToTry = combinePaths(packageRootPath, fromPaths);
}
}
// If the file is the main module, it can be imported by the package name
const mainFileRelative = packageJsonContent.typings || packageJsonContent.types || packageJsonContent.main;
if (isString(mainFileRelative)) {
const mainExportFile = toPath(mainFileRelative, packageRootPath, getCanonicalFileName);
if (removeFileExtension(mainExportFile) === removeFileExtension(getCanonicalFileName(path))) {
return packageRootPath;
if (removeFileExtension(mainExportFile) === removeFileExtension(getCanonicalFileName(moduleFileToTry))) {
return { packageRootPath, moduleFileToTry };
}
}
}
return { moduleFileToTry };
}
function getExtensionlessFileName(path: string): string {
// We still have a file name - remove the extension
const fullModulePathWithoutExtension = removeFileExtension(path);

View file

@ -0,0 +1,36 @@
/// <reference path="fourslash.ts" />
// @Filename: /project/tsconfig.json
////{
//// "compilerOptions": {
//// "jsx": "react",
//// "jsxFactory": "h"
//// }
////}
// @Filename: /project/app.tsx
////const state = useMemo(() => 'Hello', []);
// @Filename: /project/component.tsx
////import { useEffect } from "preact/hooks";
// @Filename: /project/node_modules/preact/package.json
////{ "name": "preact", "version": "10.3.4", "types": "src/index.d.ts" }
// @Filename: /project/node_modules/preact/hooks/package.json
////{ "name": "hooks", "version": "0.1.0", "types": "src/index.d.ts" }
// @Filename: /project/node_modules/preact/hooks/src/index.d.ts
////export function useEffect(): void;
////export function useMemo<T>(factory: () => T, inputs: ReadonlyArray<unknown> | undefined): T;
goTo.file("/project/app.tsx");
verify.importFixAtPosition([
getImportFixContent("preact/hooks"),
]);
function getImportFixContent(from: string) {
return `import { useMemo } from "${from}";
const state = useMemo(() => 'Hello', []);`;
}