From ef69116c41cb6805f89e6592eacb0ccb7f02207d Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 21 Oct 2022 16:05:58 -0700 Subject: [PATCH] Generate shortest `rootDirs` module specifier instead of first possible (#51244) * Generate shortest rootDirs module specifier instead of first possible * Simplify `min` --- src/compiler/core.ts | 6 ++++-- src/compiler/moduleSpecifiers.ts | 23 ++++++++++++++------- src/services/patternMatcher.ts | 2 +- tests/cases/fourslash/autoImportRootDirs.ts | 17 +++++++++++++++ 4 files changed, 37 insertions(+), 11 deletions(-) create mode 100644 tests/cases/fourslash/autoImportRootDirs.ts diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 698f7bb2fb705..870892d864031 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1937,8 +1937,10 @@ namespace ts { return compareValues(a?.start, b?.start) || compareValues(a?.length, b?.length); } - export function min(a: T, b: T, compare: Comparer): T { - return compare(a, b) === Comparison.LessThan ? a : b; + export function min(items: readonly [T, ...T[]], compare: Comparer): T; + export function min(items: readonly T[], compare: Comparer): T | undefined; + export function min(items: readonly T[], compare: Comparer): T | undefined { + return reduceLeft(items, (x, y) => compare(x, y) === Comparison.LessThan ? x : y); } /** diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index 32640fcb81579..2327f8924b8e6 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -726,16 +726,23 @@ namespace ts.moduleSpecifiers { } function tryGetModuleNameFromRootDirs(rootDirs: readonly string[], moduleFileName: string, sourceDirectory: string, getCanonicalFileName: (file: string) => string, ending: Ending, compilerOptions: CompilerOptions): string | undefined { - const normalizedTargetPath = getPathRelativeToRootDirs(moduleFileName, rootDirs, getCanonicalFileName); - if (normalizedTargetPath === undefined) { + const normalizedTargetPaths = getPathsRelativeToRootDirs(moduleFileName, rootDirs, getCanonicalFileName); + if (normalizedTargetPaths === undefined) { + return undefined; + } + + const normalizedSourcePaths = getPathsRelativeToRootDirs(sourceDirectory, rootDirs, getCanonicalFileName); + const relativePaths = flatMap(normalizedSourcePaths, sourcePath => { + return map(normalizedTargetPaths, targetPath => ensurePathIsNonModuleName(getRelativePathFromDirectory(sourcePath, targetPath, getCanonicalFileName))); + }); + const shortest = min(relativePaths, compareNumberOfDirectorySeparators); + if (!shortest) { return undefined; } - const normalizedSourcePath = getPathRelativeToRootDirs(sourceDirectory, rootDirs, getCanonicalFileName); - const relativePath = normalizedSourcePath !== undefined ? ensurePathIsNonModuleName(getRelativePathFromDirectory(normalizedSourcePath, normalizedTargetPath, getCanonicalFileName)) : normalizedTargetPath; return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs - ? removeExtensionAndIndexPostFix(relativePath, ending, compilerOptions) - : removeFileExtension(relativePath); + ? removeExtensionAndIndexPostFix(shortest, ending, compilerOptions) + : removeFileExtension(shortest); } function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCanonicalFileName, sourceDirectory }: Info, importingSourceFile: SourceFile , host: ModuleSpecifierResolutionHost, options: CompilerOptions, userPreferences: UserPreferences, packageNameOnly?: boolean, overrideMode?: ModuleKind.ESNext | ModuleKind.CommonJS): string | undefined { @@ -882,8 +889,8 @@ namespace ts.moduleSpecifiers { } } - function getPathRelativeToRootDirs(path: string, rootDirs: readonly string[], getCanonicalFileName: GetCanonicalFileName): string | undefined { - return firstDefined(rootDirs, rootDir => { + function getPathsRelativeToRootDirs(path: string, rootDirs: readonly string[], getCanonicalFileName: GetCanonicalFileName): string[] | undefined { + return mapDefined(rootDirs, rootDir => { const relativePath = getRelativePathIfInDirectory(path, rootDir, getCanonicalFileName); return relativePath !== undefined && isPathRelativeToParent(relativePath) ? undefined : relativePath; }); diff --git a/src/services/patternMatcher.ts b/src/services/patternMatcher.ts index b0071f7124098..66e4a6217933a 100644 --- a/src/services/patternMatcher.ts +++ b/src/services/patternMatcher.ts @@ -259,7 +259,7 @@ namespace ts { } function betterMatch(a: PatternMatch | undefined, b: PatternMatch | undefined): PatternMatch | undefined { - return min(a, b, compareMatches); + return min([a, b], compareMatches); } function compareMatches(a: PatternMatch | undefined, b: PatternMatch | undefined): Comparison { return a === undefined ? Comparison.GreaterThan : b === undefined ? Comparison.LessThan diff --git a/tests/cases/fourslash/autoImportRootDirs.ts b/tests/cases/fourslash/autoImportRootDirs.ts new file mode 100644 index 0000000000000..7ca66d2d7d1fe --- /dev/null +++ b/tests/cases/fourslash/autoImportRootDirs.ts @@ -0,0 +1,17 @@ +/// + +// @Filename: /tsconfig.json +//// { +//// "compilerOptions": { +//// "module": "commonjs", +//// "rootDirs": [".", "./some/other/root"] +//// } +//// } + +// @Filename: /some/other/root/types.ts +//// export type Something = {}; + +// @Filename: /index.ts +//// const s: Something/**/ + +verify.importFixModuleSpecifiers("", ["./types"]);