Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,7 @@ namespace ts {
getLibFileFromReference: notImplemented,
isSourceFileFromExternalLibrary: returnFalse,
getResolvedProjectReferenceToRedirect: returnUndefined,
getProjectReferenceRedirect: returnUndefined,
isSourceOfProjectReferenceRedirect: returnFalse,
writeFile: (name, text, writeByteOrderMark) => {
switch (name) {
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/moduleSpecifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
34 changes: 18 additions & 16 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -809,7 +809,7 @@ namespace ts {

const useSourceOfProjectReferenceRedirect = !!host.useSourceOfProjectReferenceRedirect?.() &&
!options.disableSourceOfProjectReferenceRedirect;
const onProgramCreateComplete = updateHostForUseSourceOfProjectReferenceRedirect({
const { onProgramCreateComplete, fileExists } = updateHostForUseSourceOfProjectReferenceRedirect({
compilerHost: host,
useSourceOfProjectReferenceRedirect,
toPath,
Expand Down Expand Up @@ -970,6 +970,7 @@ namespace ts {
forEachResolvedProjectReference,
isSourceOfProjectReferenceRedirect,
emitBuildInfo,
fileExists,
getProbableSymlinks,
useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(),
};
Expand Down Expand Up @@ -1479,6 +1480,7 @@ namespace ts {
getLibFileFromReference: program.getLibFileFromReference,
isSourceFileFromExternalLibrary,
getResolvedProjectReferenceToRedirect,
getProjectReferenceRedirect,
isSourceOfProjectReferenceRedirect,
getProbableSymlinks,
writeFile: writeFileCallback || (
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -3547,8 +3538,7 @@ namespace ts {
originalRealpath.call(host.compilerHost, s);
}

return onProgramCreateComplete;

return { onProgramCreateComplete, fileExists };

function onProgramCreateComplete() {
host.compilerHost.fileExists = originalFileExists;
Expand All @@ -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 ?
Expand Down
10 changes: 7 additions & 3 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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*/
Expand Down Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions src/services/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)
],
Expand Down
109 changes: 109 additions & 0 deletions src/testRunner/unittests/tsserver/projectReferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
});
}