Fix auto-import when paths points to project reference redirect (#51492)

* Fix auto-import when `paths` points to project reference redirect

* Put paths specifiers to redirects in lower priority bucket
This commit is contained in:
Andrew Branch 2022-11-15 14:19:15 -08:00 committed by GitHub
parent 3fcd1b51a1
commit 89ce16ccfd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 128 additions and 16 deletions

View File

@ -275,6 +275,7 @@ function computeModuleSpecifiers(
// 4. Relative paths
let nodeModulesSpecifiers: string[] | undefined;
let pathsSpecifiers: string[] | undefined;
let redirectPathsSpecifiers: string[] | undefined;
let relativeSpecifiers: string[] | undefined;
for (const modulePath of modulePaths) {
const specifier = tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, userPreferences, /*packageNameOnly*/ undefined, options.overrideImportMode);
@ -285,9 +286,23 @@ function computeModuleSpecifiers(
return nodeModulesSpecifiers!;
}
if (!specifier && !modulePath.isRedirect) {
const local = getLocalModuleSpecifier(modulePath.path, info, compilerOptions, host, options.overrideImportMode || importingSourceFile.impliedNodeFormat, preferences);
if (pathIsBareSpecifier(local)) {
if (!specifier) {
const local = getLocalModuleSpecifier(
modulePath.path,
info,
compilerOptions,
host,
options.overrideImportMode || importingSourceFile.impliedNodeFormat,
preferences,
/*pathsOnly*/ modulePath.isRedirect,
);
if (!local) {
continue;
}
if (modulePath.isRedirect) {
redirectPathsSpecifiers = append(redirectPathsSpecifiers, local);
}
else if (pathIsBareSpecifier(local)) {
pathsSpecifiers = append(pathsSpecifiers, local);
}
else if (!importedFileIsInNodeModules || modulePath.isInNodeModules) {
@ -306,6 +321,7 @@ function computeModuleSpecifiers(
}
return pathsSpecifiers?.length ? pathsSpecifiers :
redirectPathsSpecifiers?.length ? redirectPathsSpecifiers :
nodeModulesSpecifiers?.length ? nodeModulesSpecifiers :
Debug.checkDefined(relativeSpecifiers);
}
@ -322,32 +338,42 @@ function getInfo(importingSourceFileName: Path, host: ModuleSpecifierResolutionH
return { getCanonicalFileName, importingSourceFileName, sourceDirectory };
}
function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOptions: CompilerOptions, host: ModuleSpecifierResolutionHost, importMode: ResolutionMode, { ending, relativePreference }: Preferences): string {
function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOptions: CompilerOptions, host: ModuleSpecifierResolutionHost, importMode: ResolutionMode, { ending, relativePreference }: Preferences): string;
function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOptions: CompilerOptions, host: ModuleSpecifierResolutionHost, importMode: ResolutionMode, { ending, relativePreference }: Preferences, pathsOnly?: boolean): string | undefined;
function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOptions: CompilerOptions, host: ModuleSpecifierResolutionHost, importMode: ResolutionMode, { ending, relativePreference }: Preferences, pathsOnly?: boolean): string | undefined {
const { baseUrl, paths, rootDirs } = compilerOptions;
if (pathsOnly && !paths) {
return undefined;
}
const { sourceDirectory, getCanonicalFileName } = info;
const relativePath = rootDirs && tryGetModuleNameFromRootDirs(rootDirs, moduleFileName, sourceDirectory, getCanonicalFileName, ending, compilerOptions) ||
removeExtensionAndIndexPostFix(ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceDirectory, moduleFileName, getCanonicalFileName)), ending, compilerOptions);
if (!baseUrl && !paths || relativePreference === RelativePreference.Relative) {
return relativePath;
return pathsOnly ? undefined : relativePath;
}
const baseDirectory = getNormalizedAbsolutePath(getPathsBasePath(compilerOptions, host) || baseUrl!, host.getCurrentDirectory());
const relativeToBaseUrl = getRelativePathIfInDirectory(moduleFileName, baseDirectory, getCanonicalFileName);
if (!relativeToBaseUrl) {
return relativePath;
return pathsOnly ? undefined : relativePath;
}
const fromPaths = paths && tryGetModuleNameFromPaths(relativeToBaseUrl, paths, getAllowedEndings(ending, compilerOptions, importMode), host, compilerOptions);
const nonRelative = fromPaths === undefined && baseUrl !== undefined ? removeExtensionAndIndexPostFix(relativeToBaseUrl, ending, compilerOptions) : fromPaths;
if (!nonRelative) {
if (pathsOnly) {
return fromPaths;
}
const maybeNonRelative = fromPaths === undefined && baseUrl !== undefined ? removeExtensionAndIndexPostFix(relativeToBaseUrl, ending, compilerOptions) : fromPaths;
if (!maybeNonRelative) {
return relativePath;
}
if (relativePreference === RelativePreference.NonRelative) {
return nonRelative;
if (relativePreference === RelativePreference.NonRelative && !pathIsRelative(maybeNonRelative)) {
return maybeNonRelative;
}
if (relativePreference === RelativePreference.ExternalNonRelative) {
if (relativePreference === RelativePreference.ExternalNonRelative && !pathIsRelative(maybeNonRelative)) {
const projectDirectory = compilerOptions.configFilePath ?
toPath(getDirectoryPath(compilerOptions.configFilePath), host.getCurrentDirectory(), info.getCanonicalFileName) :
info.getCanonicalFileName(host.getCurrentDirectory());
@ -363,7 +389,7 @@ function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOpt
// lib/ | (path crosses tsconfig.json)
// imported.ts <---
//
return nonRelative;
return maybeNonRelative;
}
const nearestTargetPackageJson = getNearestAncestorDirectoryWithPackageJson(host, getDirectoryPath(modulePath));
@ -378,16 +404,14 @@ function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOpt
// package.json |
// component.ts <---
//
return nonRelative;
return maybeNonRelative;
}
return relativePath;
}
if (relativePreference !== RelativePreference.Shortest) Debug.assertNever(relativePreference);
// Prefer a relative import over a baseUrl import if it has fewer components.
return isPathRelativeToParent(nonRelative) || countPathComponents(relativePath) < countPathComponents(nonRelative) ? relativePath : nonRelative;
return isPathRelativeToParent(maybeNonRelative) || countPathComponents(relativePath) < countPathComponents(maybeNonRelative) ? relativePath : maybeNonRelative;
}
/** @internal */

View File

@ -3041,6 +3041,10 @@ export class TestState {
* @param fileName Path to file where error should be retrieved from.
*/
private getCodeFixes(fileName: string, errorCode?: number, preferences: ts.UserPreferences = ts.emptyOptions, position?: number): readonly ts.CodeFixAction[] {
if (this.testType === FourSlashTestType.Server) {
this.configure(preferences);
}
const diagnosticsForCodeFix = this.getDiagnostics(fileName, /*includeSuggestions*/ true).map(diagnostic => ({
start: diagnostic.start,
length: diagnostic.length,

View File

@ -0,0 +1,41 @@
/// <reference path="../fourslash.ts" />
// @Filename: /common/tsconfig.json
//// {
//// "compilerOptions": {
//// "module": "commonjs",
//// "outDir": "dist",
//// "composite": true
//// },
//// "include": ["src"]
//// }
// @Filename: /common/src/MyModule.ts
//// export function square(n: number) {
//// return n * 2;
//// }
// @Filename: /web/tsconfig.json
//// {
//// "compilerOptions": {
//// "module": "esnext",
//// "moduleResolution": "node",
//// "noEmit": true,
//// "baseUrl": "."
//// },
//// "include": ["src"],
//// "references": [{ "path": "../common" }]
//// }
// @Filename: /web/src/MyApp.ts
//// import { square } from "../../common/dist/src/MyModule";
// @Filename: /web/src/Helper.ts
//// export function saveMe() {
//// square/**/(2);
//// }
goTo.file("/web/src/Helper.ts");
verify.importFixModuleSpecifiers("", ["../../common/src/MyModule"], {
importModuleSpecifierPreference: "non-relative"
});

View File

@ -0,0 +1,43 @@
/// <reference path="../fourslash.ts" />
// @Filename: /common/tsconfig.json
//// {
//// "compilerOptions": {
//// "module": "commonjs",
//// "outDir": "dist",
//// "composite": true
//// },
//// "include": ["src"]
//// }
// @Filename: /common/src/MyModule.ts
//// export function square(n: number) {
//// return n * 2;
//// }
// @Filename: /web/tsconfig.json
//// {
//// "compilerOptions": {
//// "module": "esnext",
//// "moduleResolution": "node",
//// "noEmit": true,
//// "paths": {
//// "@common/*": ["../common/dist/src/*"]
//// }
//// },
//// "include": ["src"],
//// "references": [{ "path": "../common" }]
//// }
// @Filename: /web/src/MyApp.ts
//// import { square } from "@common/MyModule";
// @Filename: /web/src/Helper.ts
//// export function saveMe() {
//// square/**/(2);
//// }
goTo.file("/web/src/Helper.ts");
verify.importFixModuleSpecifiers("", ["@common/MyModule"], {
importModuleSpecifierPreference: "non-relative"
});