mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-12 11:08:26 -05:00
Handle auto import scenarios when using project references (#37482)
* Add test for project reference and auto import Test for #34677 * Add project reference redirect to the possible file name to import if file is source of project reference redirect Fixes the auto import suggestion when project is built * Use fileExists that mimics presence of project reference redirect file when trying to get auto import file name
This commit is contained in:
@@ -4070,6 +4070,9 @@ namespace ts {
|
||||
getProbableSymlinks: maybeBind(host, host.getProbableSymlinks),
|
||||
useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames),
|
||||
redirectTargetsMap: host.redirectTargetsMap,
|
||||
getProjectReferenceRedirect: fileName => host.getProjectReferenceRedirect(fileName),
|
||||
isSourceOfProjectReferenceRedirect: fileName => host.isSourceOfProjectReferenceRedirect(fileName),
|
||||
fileExists: fileName => host.fileExists(fileName),
|
||||
} : undefined },
|
||||
encounteredError: false,
|
||||
visitedTypes: undefined,
|
||||
|
||||
@@ -759,6 +759,7 @@ namespace ts {
|
||||
getLibFileFromReference: notImplemented,
|
||||
isSourceFileFromExternalLibrary: returnFalse,
|
||||
getResolvedProjectReferenceToRedirect: returnUndefined,
|
||||
getProjectReferenceRedirect: returnUndefined,
|
||||
isSourceOfProjectReferenceRedirect: returnFalse,
|
||||
writeFile: (name, text, writeByteOrderMark) => {
|
||||
switch (name) {
|
||||
|
||||
@@ -177,8 +177,9 @@ namespace ts.moduleSpecifiers {
|
||||
): T | undefined {
|
||||
const getCanonicalFileName = hostGetCanonicalFileName(host);
|
||||
const cwd = host.getCurrentDirectory();
|
||||
const referenceRedirect = host.isSourceOfProjectReferenceRedirect(importedFileName) ? host.getProjectReferenceRedirect(importedFileName) : undefined;
|
||||
const redirects = host.redirectTargetsMap.get(toPath(importedFileName, cwd, getCanonicalFileName)) || emptyArray;
|
||||
const importedFileNames = [importedFileName, ...redirects];
|
||||
const importedFileNames = [...(referenceRedirect ? [referenceRedirect] : emptyArray), importedFileName, ...redirects];
|
||||
const targets = importedFileNames.map(f => getNormalizedAbsolutePath(f, cwd));
|
||||
if (!preferSymlinks) {
|
||||
const result = forEach(targets, cb);
|
||||
|
||||
@@ -809,7 +809,7 @@ namespace ts {
|
||||
|
||||
const useSourceOfProjectReferenceRedirect = !!host.useSourceOfProjectReferenceRedirect?.() &&
|
||||
!options.disableSourceOfProjectReferenceRedirect;
|
||||
const onProgramCreateComplete = updateHostForUseSourceOfProjectReferenceRedirect({
|
||||
const { onProgramCreateComplete, fileExists } = updateHostForUseSourceOfProjectReferenceRedirect({
|
||||
compilerHost: host,
|
||||
useSourceOfProjectReferenceRedirect,
|
||||
toPath,
|
||||
@@ -970,6 +970,7 @@ namespace ts {
|
||||
forEachResolvedProjectReference,
|
||||
isSourceOfProjectReferenceRedirect,
|
||||
emitBuildInfo,
|
||||
fileExists,
|
||||
getProbableSymlinks,
|
||||
useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(),
|
||||
};
|
||||
@@ -1479,6 +1480,7 @@ namespace ts {
|
||||
getLibFileFromReference: program.getLibFileFromReference,
|
||||
isSourceFileFromExternalLibrary,
|
||||
getResolvedProjectReferenceToRedirect,
|
||||
getProjectReferenceRedirect,
|
||||
isSourceOfProjectReferenceRedirect,
|
||||
getProbableSymlinks,
|
||||
writeFile: writeFileCallback || (
|
||||
@@ -3482,20 +3484,9 @@ namespace ts {
|
||||
const originalGetDirectories = host.compilerHost.getDirectories;
|
||||
const originalRealpath = host.compilerHost.realpath;
|
||||
|
||||
if (!host.useSourceOfProjectReferenceRedirect) return { onProgramCreateComplete: noop, fileExists };
|
||||
|
||||
if (!host.useSourceOfProjectReferenceRedirect) return noop;
|
||||
|
||||
// This implementation of fileExists checks if the file being requested is
|
||||
// .d.ts file for the referenced Project.
|
||||
// If it is it returns true irrespective of whether that file exists on host
|
||||
host.compilerHost.fileExists = (file) => {
|
||||
if (originalFileExists.call(host.compilerHost, file)) return true;
|
||||
if (!host.getResolvedProjectReferences()) return false;
|
||||
if (!isDeclarationFileName(file)) return false;
|
||||
|
||||
// Project references go to source file instead of .d.ts file
|
||||
return fileOrDirectoryExistsUsingSource(file, /*isFile*/ true);
|
||||
};
|
||||
host.compilerHost.fileExists = fileExists;
|
||||
|
||||
if (originalDirectoryExists) {
|
||||
// This implementation of directoryExists checks if the directory being requested is
|
||||
@@ -3547,8 +3538,7 @@ namespace ts {
|
||||
originalRealpath.call(host.compilerHost, s);
|
||||
}
|
||||
|
||||
return onProgramCreateComplete;
|
||||
|
||||
return { onProgramCreateComplete, fileExists };
|
||||
|
||||
function onProgramCreateComplete() {
|
||||
host.compilerHost.fileExists = originalFileExists;
|
||||
@@ -3557,6 +3547,18 @@ namespace ts {
|
||||
// DO not revert realpath as it could be used later
|
||||
}
|
||||
|
||||
// This implementation of fileExists checks if the file being requested is
|
||||
// .d.ts file for the referenced Project.
|
||||
// If it is it returns true irrespective of whether that file exists on host
|
||||
function fileExists(file: string) {
|
||||
if (originalFileExists.call(host.compilerHost, file)) return true;
|
||||
if (!host.getResolvedProjectReferences()) return false;
|
||||
if (!isDeclarationFileName(file)) return false;
|
||||
|
||||
// Project references go to source file instead of .d.ts file
|
||||
return fileOrDirectoryExistsUsingSource(file, /*isFile*/ true);
|
||||
}
|
||||
|
||||
function fileExistsIfProjectReferenceDts(file: string) {
|
||||
const source = host.getSourceOfProjectReferenceRedirect(file);
|
||||
return source !== undefined ?
|
||||
|
||||
@@ -3287,6 +3287,10 @@ namespace ts {
|
||||
/*@internal*/ getProgramBuildInfo?(): ProgramBuildInfo | undefined;
|
||||
/*@internal*/ emitBuildInfo(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult;
|
||||
/*@internal*/ getProbableSymlinks(): ReadonlyMap<string>;
|
||||
/**
|
||||
* This implementation handles file exists to be true if file is source of project reference redirect when program is created using useSourceOfProjectReferenceRedirect
|
||||
*/
|
||||
/*@internal*/ fileExists(fileName: string): boolean;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
@@ -6428,16 +6432,16 @@ namespace ts {
|
||||
/*@internal*/
|
||||
export interface ModuleSpecifierResolutionHost {
|
||||
useCaseSensitiveFileNames?(): boolean;
|
||||
fileExists?(path: string): boolean;
|
||||
fileExists(path: string): boolean;
|
||||
getCurrentDirectory(): string;
|
||||
readFile?(path: string): string | undefined;
|
||||
/* @internal */
|
||||
getProbableSymlinks?(files: readonly SourceFile[]): ReadonlyMap<string>;
|
||||
/* @internal */
|
||||
getGlobalTypingsCacheLocation?(): string | undefined;
|
||||
|
||||
getSourceFiles(): readonly SourceFile[];
|
||||
readonly redirectTargetsMap: RedirectTargetsMap;
|
||||
getProjectReferenceRedirect(fileName: string): string | undefined;
|
||||
isSourceOfProjectReferenceRedirect(fileName: string): boolean;
|
||||
}
|
||||
|
||||
// Note: this used to be deprecated in our public API, but is still used internally
|
||||
|
||||
@@ -1705,14 +1705,16 @@ namespace ts {
|
||||
// Mix in `getProbableSymlinks` from Program when host doesn't have it
|
||||
// in order for non-Project hosts to have a symlinks cache.
|
||||
return {
|
||||
fileExists: maybeBind(host, host.fileExists),
|
||||
fileExists: fileName => program.fileExists(fileName),
|
||||
getCurrentDirectory: () => host.getCurrentDirectory(),
|
||||
readFile: maybeBind(host, host.readFile),
|
||||
useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames),
|
||||
getProbableSymlinks: maybeBind(host, host.getProbableSymlinks) || (() => program.getProbableSymlinks()),
|
||||
getGlobalTypingsCacheLocation: maybeBind(host, host.getGlobalTypingsCacheLocation),
|
||||
getSourceFiles: () => program.getSourceFiles(),
|
||||
redirectTargetsMap: program.redirectTargetsMap
|
||||
redirectTargetsMap: program.redirectTargetsMap,
|
||||
getProjectReferenceRedirect: fileName => program.getProjectReferenceRedirect(fileName),
|
||||
isSourceOfProjectReferenceRedirect: fileName => program.isSourceOfProjectReferenceRedirect(fileName),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -353,7 +353,7 @@ fnErr();
|
||||
{ line: 4, offset: 5 },
|
||||
{ line: 4, offset: 10 },
|
||||
Diagnostics.Module_0_has_no_exported_member_1,
|
||||
[`"../dependency/fns"`, "fnErr"],
|
||||
[`"../decls/fns"`, "fnErr"],
|
||||
"error",
|
||||
)
|
||||
],
|
||||
|
||||
@@ -2173,5 +2173,114 @@ foo;`
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("auto import with referenced project", () => {
|
||||
function verifyAutoImport(built: boolean, disableSourceOfProjectReferenceRedirect?: boolean) {
|
||||
const solnConfig: File = {
|
||||
path: `${tscWatch.projectRoot}/tsconfig.json`,
|
||||
content: JSON.stringify({
|
||||
files: [],
|
||||
references: [
|
||||
{ path: "shared/src/library" },
|
||||
{ path: "app/src/program" }
|
||||
]
|
||||
})
|
||||
};
|
||||
const sharedConfig: File = {
|
||||
path: `${tscWatch.projectRoot}/shared/src/library/tsconfig.json`,
|
||||
content: JSON.stringify({
|
||||
compilerOptions: {
|
||||
composite: true,
|
||||
outDir: "../../bld/library"
|
||||
}
|
||||
})
|
||||
};
|
||||
const sharedIndex: File = {
|
||||
path: `${tscWatch.projectRoot}/shared/src/library/index.ts`,
|
||||
content: `export function foo() {}`
|
||||
};
|
||||
const sharedPackage: File = {
|
||||
path: `${tscWatch.projectRoot}/shared/package.json`,
|
||||
content: JSON.stringify({
|
||||
name: "shared",
|
||||
version: "1.0.0",
|
||||
main: "bld/library/index.js",
|
||||
types: "bld/library/index.d.ts"
|
||||
})
|
||||
};
|
||||
const appConfig: File = {
|
||||
path: `${tscWatch.projectRoot}/app/src/program/tsconfig.json`,
|
||||
content: JSON.stringify({
|
||||
compilerOptions: {
|
||||
composite: true,
|
||||
outDir: "../../bld/program",
|
||||
disableSourceOfProjectReferenceRedirect
|
||||
},
|
||||
references: [
|
||||
{ path: "../../../shared/src/library" }
|
||||
]
|
||||
})
|
||||
};
|
||||
const appBar: File = {
|
||||
path: `${tscWatch.projectRoot}/app/src/program/bar.ts`,
|
||||
content: `import {foo} from "shared";`
|
||||
};
|
||||
const appIndex: File = {
|
||||
path: `${tscWatch.projectRoot}/app/src/program/index.ts`,
|
||||
content: `foo`
|
||||
};
|
||||
const sharedSymlink: SymLink = {
|
||||
path: `${tscWatch.projectRoot}/node_modules/shared`,
|
||||
symLink: `${tscWatch.projectRoot}/shared`
|
||||
};
|
||||
const files = [solnConfig, sharedConfig, sharedIndex, sharedPackage, appConfig, appBar, appIndex, sharedSymlink, libFile];
|
||||
const host = createServerHost(files);
|
||||
if (built) {
|
||||
const solutionBuilder = tscWatch.createSolutionBuilder(host, [solnConfig.path], {});
|
||||
solutionBuilder.build();
|
||||
host.clearOutput();
|
||||
}
|
||||
const session = createSession(host);
|
||||
openFilesForSession([appIndex], session);
|
||||
const response = session.executeCommandSeq<protocol.CodeFixRequest>({
|
||||
command: protocol.CommandTypes.GetCodeFixes,
|
||||
arguments: {
|
||||
file: appIndex.path,
|
||||
startLine: 1,
|
||||
startOffset: 1,
|
||||
endLine: 1,
|
||||
endOffset: 4,
|
||||
errorCodes: [Diagnostics.Cannot_find_name_0.code],
|
||||
}
|
||||
}).response as protocol.CodeFixAction[];
|
||||
assert.deepEqual(response, [
|
||||
{
|
||||
fixName: "import",
|
||||
description: `Import 'foo' from module "shared"`,
|
||||
changes: [{
|
||||
fileName: appIndex.path,
|
||||
textChanges: [{
|
||||
start: { line: 1, offset: 1 },
|
||||
end: { line: 1, offset: 1 },
|
||||
newText: 'import { foo } from "shared";\n\n',
|
||||
}],
|
||||
}],
|
||||
commands: undefined,
|
||||
fixAllDescription: undefined,
|
||||
fixId: undefined
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
it("when project is built", () => {
|
||||
verifyAutoImport(/*built*/ true);
|
||||
});
|
||||
it("when project is not built", () => {
|
||||
verifyAutoImport(/*built*/ false);
|
||||
});
|
||||
it("when disableSourceOfProjectReferenceRedirect is true", () => {
|
||||
verifyAutoImport(/*built*/ true, /*disableSourceOfProjectReferenceRedirect*/ true);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user