Merge pull request #32028 from microsoft/referencesPrototypeSourceFile

For editing experience, use source instead of .d.ts files from project references
This commit is contained in:
Sheetal Nandi 2019-09-24 13:16:53 -07:00 committed by GitHub
commit 992c211c22
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 2225 additions and 604 deletions

View file

@ -425,7 +425,7 @@ namespace ts {
const options = program.getCompilerOptions();
forEach(program.getSourceFiles(), f =>
program.isSourceFileDefaultLibrary(f) &&
!skipTypeChecking(f, options) &&
!skipTypeChecking(f, options, program) &&
removeSemanticDiagnosticsOf(state, f.path)
);
}

View file

@ -572,7 +572,7 @@ namespace ts {
return node && getTypeArgumentConstraint(node);
},
getSuggestionDiagnostics: (file, ct) => {
if (skipTypeChecking(file, compilerOptions)) {
if (skipTypeChecking(file, compilerOptions, host)) {
return emptyArray;
}
@ -31048,7 +31048,7 @@ namespace ts {
function checkSourceFileWorker(node: SourceFile) {
const links = getNodeLinks(node);
if (!(links.flags & NodeCheckFlags.TypeChecked)) {
if (skipTypeChecking(node, compilerOptions)) {
if (skipTypeChecking(node, compilerOptions, host)) {
return;
}

View file

@ -772,6 +772,12 @@ namespace ts {
category: Diagnostics.Advanced_Options,
description: Diagnostics.Disable_size_limitations_on_JavaScript_projects
},
{
name: "disableSourceOfProjectReferenceRedirect",
type: "boolean",
category: Diagnostics.Advanced_Options,
description: Diagnostics.Disable_use_of_source_files_instead_of_declaration_files_from_referenced_projects
},
{
name: "noImplicitUseStrict",
type: "boolean",

View file

@ -4036,6 +4036,10 @@
"category": "Message",
"code": 6220
},
"Disable use of source files instead of declaration files from referenced projects.": {
"category": "Message",
"code": 6221
},
"Projects to reference": {
"category": "Message",

View file

@ -817,6 +817,8 @@ namespace ts {
let resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined;
let projectReferenceRedirects: Map<ResolvedProjectReference | false> | undefined;
let mapFromFileToProjectReferenceRedirects: Map<Path> | undefined;
let mapFromToProjectReferenceRedirectSource: Map<SourceOfProjectReferenceRedirect> | undefined;
const useSourceOfProjectReferenceRedirect = !!host.useSourceOfProjectReferenceRedirect && host.useSourceOfProjectReferenceRedirect();
const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options);
// We set `structuralIsReused` to `undefined` because `tryReuseStructureFromOldProgram` calls `tryReuseStructureFromOldProgram` which checks
@ -831,17 +833,32 @@ namespace ts {
if (!resolvedProjectReferences) {
resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile);
}
if (host.setResolvedProjectReferenceCallbacks) {
host.setResolvedProjectReferenceCallbacks({
getSourceOfProjectReferenceRedirect,
forEachResolvedProjectReference
});
}
if (rootNames.length) {
for (const parsedRef of resolvedProjectReferences) {
if (!parsedRef) continue;
const out = parsedRef.commandLine.options.outFile || parsedRef.commandLine.options.out;
if (out) {
processSourceFile(changeExtension(out, ".d.ts"), /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined);
if (useSourceOfProjectReferenceRedirect) {
if (out || getEmitModuleKind(parsedRef.commandLine.options) === ModuleKind.None) {
for (const fileName of parsedRef.commandLine.fileNames) {
processSourceFile(fileName, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined);
}
}
}
else if (getEmitModuleKind(parsedRef.commandLine.options) === ModuleKind.None) {
for (const fileName of parsedRef.commandLine.fileNames) {
if (!fileExtensionIs(fileName, Extension.Dts) && hasTSFileExtension(fileName)) {
processSourceFile(getOutputDeclarationFileName(fileName, parsedRef.commandLine, !host.useCaseSensitiveFileNames()), /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined);
else {
if (out) {
processSourceFile(changeExtension(out, ".d.ts"), /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined);
}
else if (getEmitModuleKind(parsedRef.commandLine.options) === ModuleKind.None) {
for (const fileName of parsedRef.commandLine.fileNames) {
if (!fileExtensionIs(fileName, Extension.Dts) && hasTSFileExtension(fileName)) {
processSourceFile(getOutputDeclarationFileName(fileName, parsedRef.commandLine, !host.useCaseSensitiveFileNames()), /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined);
}
}
}
}
@ -955,6 +972,7 @@ namespace ts {
getResolvedProjectReferenceToRedirect,
getResolvedProjectReferenceByPath,
forEachResolvedProjectReference,
isSourceOfProjectReferenceRedirect,
emitBuildInfo
};
@ -987,9 +1005,15 @@ namespace ts {
return ts.toPath(fileName, currentDirectory, getCanonicalFileName);
}
function isValidSourceFileForEmit(file: SourceFile) {
// source file is allowed to be emitted and its not source of project reference redirect
return sourceFileMayBeEmitted(file, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect) &&
!isSourceOfProjectReferenceRedirect(file.fileName);
}
function getCommonSourceDirectory() {
if (commonSourceDirectory === undefined) {
const emittedFiles = filter(files, file => sourceFileMayBeEmitted(file, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect));
const emittedFiles = filter(files, file => isValidSourceFileForEmit(file));
if (options.rootDir && checkSourceFilesBelongToPath(emittedFiles, options.rootDir)) {
// If a rootDir is specified use it as the commonSourceDirectory
commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, currentDirectory);
@ -1220,6 +1244,12 @@ namespace ts {
}
if (projectReferences) {
resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile);
if (host.setResolvedProjectReferenceCallbacks) {
host.setResolvedProjectReferenceCallbacks({
getSourceOfProjectReferenceRedirect,
forEachResolvedProjectReference
});
}
}
// check if program source files has changed in the way that can affect structure of the program
@ -1403,6 +1433,13 @@ namespace ts {
for (const newSourceFile of newSourceFiles) {
const filePath = newSourceFile.path;
addFileToFilesByName(newSourceFile, filePath, newSourceFile.resolvedPath);
if (useSourceOfProjectReferenceRedirect) {
const redirectProject = getProjectReferenceRedirectProject(newSourceFile.fileName);
if (redirectProject && !(redirectProject.commandLine.options.outFile || redirectProject.commandLine.options.out)) {
const redirect = getProjectReferenceOutputName(redirectProject, newSourceFile.fileName);
addFileToFilesByName(newSourceFile, toPath(redirect), /*redirectedPath*/ undefined);
}
}
// Set the file as found during node modules search if it was found that way in old progra,
if (oldProgram.isSourceFileFromExternalLibrary(oldProgram.getSourceFileByPath(newSourceFile.resolvedPath)!)) {
sourceFilesFoundSearchingNodeModules.set(filePath, true);
@ -1682,7 +1719,7 @@ namespace ts {
function getSemanticDiagnosticsForFileNoCache(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] | undefined {
return runWithCancellationToken(() => {
if (skipTypeChecking(sourceFile, options)) {
if (skipTypeChecking(sourceFile, options, program)) {
return emptyArray;
}
@ -2234,6 +2271,16 @@ namespace ts {
// Get source file from normalized fileName
function findSourceFile(fileName: string, path: Path, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, refFile: RefFile | undefined, packageId: PackageId | undefined): SourceFile | undefined {
if (useSourceOfProjectReferenceRedirect) {
const source = getSourceOfProjectReferenceRedirect(fileName);
if (source) {
const file = isString(source) ?
findSourceFile(source, toPath(source), isDefaultLib, ignoreNoDefaultLib, refFile, packageId) :
undefined;
if (file) addFileToFilesByName(file, path, /*redirectedPath*/ undefined);
return file;
}
}
const originalFileName = fileName;
if (filesByName.has(path)) {
const file = filesByName.get(path);
@ -2282,7 +2329,7 @@ namespace ts {
}
let redirectedPath: Path | undefined;
if (refFile) {
if (refFile && !useSourceOfProjectReferenceRedirect) {
const redirectProject = getProjectReferenceRedirectProject(fileName);
if (redirectProject) {
if (redirectProject.commandLine.options.outFile || redirectProject.commandLine.options.out) {
@ -2451,6 +2498,36 @@ namespace ts {
});
}
function getSourceOfProjectReferenceRedirect(file: string) {
if (!isDeclarationFileName(file)) return undefined;
if (mapFromToProjectReferenceRedirectSource === undefined) {
mapFromToProjectReferenceRedirectSource = createMap();
forEachResolvedProjectReference(resolvedRef => {
if (resolvedRef) {
const out = resolvedRef.commandLine.options.outFile || resolvedRef.commandLine.options.out;
if (out) {
// Dont know which source file it means so return true?
const outputDts = changeExtension(out, Extension.Dts);
mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), true);
}
else {
forEach(resolvedRef.commandLine.fileNames, fileName => {
if (!fileExtensionIs(fileName, Extension.Dts) && hasTSFileExtension(fileName)) {
const outputDts = getOutputDeclarationFileName(fileName, resolvedRef.commandLine, host.useCaseSensitiveFileNames());
mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), fileName);
}
});
}
}
});
}
return mapFromToProjectReferenceRedirectSource.get(toPath(file));
}
function isSourceOfProjectReferenceRedirect(fileName: string) {
return useSourceOfProjectReferenceRedirect && !!getResolvedProjectReferenceToRedirect(fileName);
}
function forEachProjectReference<T>(
projectReferences: readonly ProjectReference[] | undefined,
resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined,
@ -2858,8 +2935,7 @@ namespace ts {
const rootPaths = arrayToSet(rootNames, toPath);
for (const file of files) {
// Ignore file that is not emitted
if (!sourceFileMayBeEmitted(file, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect)) continue;
if (!rootPaths.has(file.path)) {
if (isValidSourceFileForEmit(file) && !rootPaths.has(file.path)) {
addProgramDiagnosticAtRefPath(
file,
rootPaths,

View file

@ -522,6 +522,33 @@ namespace ts {
}
}
function recursiveCreateDirectory(directoryPath: string, sys: System) {
const basePath = getDirectoryPath(directoryPath);
const shouldCreateParent = basePath !== "" && directoryPath !== basePath && !sys.directoryExists(basePath);
if (shouldCreateParent) {
recursiveCreateDirectory(basePath, sys);
}
if (shouldCreateParent || !sys.directoryExists(directoryPath)) {
sys.createDirectory(directoryPath);
}
}
/**
* patch writefile to create folder before writing the file
*/
/*@internal*/
export function patchWriteFileEnsuringDirectory(sys: System) {
// patch writefile to create folder before writing the file
const originalWriteFile = sys.writeFile;
sys.writeFile = (path, data, writeBom) => {
const directoryPath = getDirectoryPath(normalizeSlashes(path));
if (directoryPath && !sys.directoryExists(directoryPath)) {
recursiveCreateDirectory(directoryPath, sys);
}
originalWriteFile.call(sys, path, data, writeBom);
};
}
/*@internal*/
export type BufferEncoding = "ascii" | "utf8" | "utf-8" | "utf16le" | "ucs2" | "ucs-2" | "base64" | "latin1" | "binary" | "hex";
@ -1365,17 +1392,6 @@ namespace ts {
};
}
function recursiveCreateDirectory(directoryPath: string, sys: System) {
const basePath = getDirectoryPath(directoryPath);
const shouldCreateParent = basePath !== "" && directoryPath !== basePath && !sys.directoryExists(basePath);
if (shouldCreateParent) {
recursiveCreateDirectory(basePath, sys);
}
if (shouldCreateParent || !sys.directoryExists(directoryPath)) {
sys.createDirectory(directoryPath);
}
}
let sys: System | undefined;
if (typeof ChakraHost !== "undefined") {
sys = getChakraSystem();
@ -1387,14 +1403,7 @@ namespace ts {
}
if (sys) {
// patch writefile to create folder before writing the file
const originalWriteFile = sys.writeFile;
sys.writeFile = (path, data, writeBom) => {
const directoryPath = getDirectoryPath(normalizeSlashes(path));
if (directoryPath && !sys!.directoryExists(directoryPath)) {
recursiveCreateDirectory(directoryPath, sys!);
}
originalWriteFile.call(sys, path, data, writeBom);
};
patchWriteFileEnsuringDirectory(sys);
}
return sys!;
})();

View file

@ -3065,6 +3065,7 @@ namespace ts {
/*@internal*/ getResolvedProjectReferenceToRedirect(fileName: string): ResolvedProjectReference | undefined;
/*@internal*/ forEachResolvedProjectReference<T>(cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined): T | undefined;
/*@internal*/ getResolvedProjectReferenceByPath(projectReferencePath: Path): ResolvedProjectReference | undefined;
/*@internal*/ isSourceOfProjectReferenceRedirect(fileName: string): boolean;
/*@internal*/ getProgramBuildInfo?(): ProgramBuildInfo | undefined;
/*@internal*/ emitBuildInfo(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult;
}
@ -3165,6 +3166,7 @@ namespace ts {
getSourceFile(fileName: string): SourceFile | undefined;
getResolvedTypeReferenceDirectives(): ReadonlyMap<ResolvedTypeReferenceDirective | undefined>;
getProjectReferenceRedirect(fileName: string): string | undefined;
isSourceOfProjectReferenceRedirect(fileName: string): boolean;
readonly redirectTargetsMap: RedirectTargetsMap;
}
@ -4775,6 +4777,7 @@ namespace ts {
/* @internal */ diagnostics?: boolean;
/* @internal */ extendedDiagnostics?: boolean;
disableSizeLimit?: boolean;
disableSourceOfProjectReferenceRedirect?: boolean;
downlevelIteration?: boolean;
emitBOM?: boolean;
emitDecoratorMetadata?: boolean;
@ -5303,11 +5306,23 @@ namespace ts {
/* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean;
createHash?(data: string): string;
getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined;
/* @internal */ setResolvedProjectReferenceCallbacks?(callbacks: ResolvedProjectReferenceCallbacks): void;
/* @internal */ useSourceOfProjectReferenceRedirect?(): boolean;
// TODO: later handle this in better way in builder host instead once the api for tsbuild finalizes and doesn't use compilerHost as base
/*@internal*/createDirectory?(directory: string): void;
}
/** true if --out otherwise source file name */
/*@internal*/
export type SourceOfProjectReferenceRedirect = string | true;
/*@internal*/
export interface ResolvedProjectReferenceCallbacks {
getSourceOfProjectReferenceRedirect(fileName: string): SourceOfProjectReferenceRedirect | undefined;
forEachResolvedProjectReference<T>(cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined): T | undefined;
}
/* @internal */
export const enum TransformFlags {
None = 0,

View file

@ -8713,11 +8713,16 @@ namespace ts {
return { pos: typeParameters.pos - 1, end: typeParameters.end + 1 };
}
export function skipTypeChecking(sourceFile: SourceFile, options: CompilerOptions) {
export interface HostWithIsSourceOfProjectReferenceRedirect {
isSourceOfProjectReferenceRedirect(fileName: string): boolean;
}
export function skipTypeChecking(sourceFile: SourceFile, options: CompilerOptions, host: HostWithIsSourceOfProjectReferenceRedirect) {
// If skipLibCheck is enabled, skip reporting errors if file is a declaration file.
// If skipDefaultLibCheck is enabled, skip reporting errors if file contains a
// '/// <reference no-default-lib="true"/>' directive.
return options.skipLibCheck && sourceFile.isDeclarationFile || options.skipDefaultLibCheck && sourceFile.hasNoDefaultLib;
return (options.skipLibCheck && sourceFile.isDeclarationFile ||
options.skipDefaultLibCheck && sourceFile.hasNoDefaultLib) ||
host.isSourceOfProjectReferenceRedirect(sourceFile.fileName);
}
export function isJsonEqual(a: unknown, b: unknown): boolean {

View file

@ -44,7 +44,10 @@ interface Array<T> { length: number; [n: number]: T; }`
}
export function createServerHost(fileOrFolderList: readonly FileOrFolderOrSymLink[], params?: TestServerHostCreationParameters): TestServerHost {
return new TestServerHost(/*withSafelist*/ true, fileOrFolderList, params);
const host = new TestServerHost(/*withSafelist*/ true, fileOrFolderList, params);
// Just like sys, patch the host to use writeFile
patchWriteFileEnsuringDirectory(host);
return host;
}
export interface File {
@ -174,8 +177,8 @@ interface Array<T> { length: number; [n: number]: T; }`
}
}
export function checkWatchedFiles(host: TestServerHost, expectedFiles: string[]) {
checkMapKeys("watchedFiles", host.watchedFiles, expectedFiles);
export function checkWatchedFiles(host: TestServerHost, expectedFiles: string[], additionalInfo?: string) {
checkMapKeys(`watchedFiles:: ${additionalInfo || ""}::`, host.watchedFiles, expectedFiles);
}
export function checkWatchedFilesDetailed(host: TestServerHost, expectedFiles: ReadonlyMap<number>): void;
@ -1016,6 +1019,19 @@ interface Array<T> { length: number; [n: number]: T; }`
}
}
export type TestServerHostTrackingWrittenFiles = TestServerHost & { writtenFiles: Map<true>; };
export function changeToHostTrackingWrittenFiles(inputHost: TestServerHost) {
const host = inputHost as TestServerHostTrackingWrittenFiles;
const originalWriteFile = host.writeFile;
host.writtenFiles = createMap<true>();
host.writeFile = (fileName, content) => {
originalWriteFile.call(host, fileName, content);
const path = host.toFullPath(fileName);
host.writtenFiles.set(path, true);
};
return host;
}
export const tsbuildProjectsLocation = "/user/username/projects";
export function getTsBuildProjectFilePath(project: string, file: string) {
return `${tsbuildProjectsLocation}/${project}/${file}`;

View file

@ -1777,6 +1777,12 @@ namespace ts.server {
configFileErrors.push(...parsedCommandLine.errors);
}
this.logger.info(`Config: ${configFilename} : ${JSON.stringify({
rootNames: parsedCommandLine.fileNames,
options: parsedCommandLine.options,
projectReferences: parsedCommandLine.projectReferences
}, /*replacer*/ undefined, " ")}`);
Debug.assert(!!parsedCommandLine.fileNames);
const compilerOptions = parsedCommandLine.options;
@ -1818,7 +1824,7 @@ namespace ts.server {
let scriptInfo: ScriptInfo | NormalizedPath;
let path: Path;
// Use the project's fileExists so that it can use caching instead of reaching to disk for the query
if (!isDynamic && !project.fileExists(newRootFile)) {
if (!isDynamic && !project.fileExistsWithCache(newRootFile)) {
path = normalizedPathToPath(normalizedPath, this.currentDirectory, this.toCanonicalFileName);
const existingValue = projectRootFilesMap.get(path)!;
if (isScriptInfo(existingValue)) {
@ -1851,7 +1857,7 @@ namespace ts.server {
projectRootFilesMap.forEach((value, path) => {
if (!newRootScriptInfoMap.has(path)) {
if (isScriptInfo(value)) {
project.removeFile(value, project.fileExists(path), /*detachFromProject*/ true);
project.removeFile(value, project.fileExistsWithCache(path), /*detachFromProject*/ true);
}
else {
projectRootFilesMap.delete(path);
@ -2584,7 +2590,9 @@ namespace ts.server {
/*@internal*/
getOriginalLocationEnsuringConfiguredProject(project: Project, location: DocumentPosition): DocumentPosition | undefined {
const originalLocation = project.getSourceMapper().tryGetSourcePosition(location);
const originalLocation = project.isSourceOfProjectReferenceRedirect(location.fileName) ?
location :
project.getSourceMapper().tryGetSourcePosition(location);
if (!originalLocation) return undefined;
const { fileName } = originalLocation;
@ -2595,7 +2603,8 @@ namespace ts.server {
if (!configFileName) return undefined;
const configuredProject = this.findConfiguredProjectByProjectName(configFileName) ||
this.createAndLoadConfiguredProject(configFileName, `Creating project for original file: ${originalFileInfo.fileName} for location: ${location.fileName}`);
this.createAndLoadConfiguredProject(configFileName, `Creating project for original file: ${originalFileInfo.fileName}${location !== originalLocation ? " for location: " + location.fileName : ""}`);
if (configuredProject === project) return originalLocation;
updateProjectIfDirty(configuredProject);
// Keep this configured project as referenced from project
addOriginalConfiguredProject(configuredProject);

View file

@ -196,6 +196,11 @@ namespace ts.server {
/*@internal*/
originalConfiguredProjects: Map<true> | undefined;
/*@internal*/
getResolvedProjectReferenceToRedirect(_fileName: string): ResolvedProjectReference | undefined {
return undefined;
}
private readonly cancellationToken: ThrottledCancellationToken;
public isNonTsProject() {
@ -391,6 +396,11 @@ namespace ts.server {
}
fileExists(file: string): boolean {
return this.fileExistsWithCache(file);
}
/* @internal */
fileExistsWithCache(file: string): boolean {
// As an optimization, don't hit the disks for files we already know don't exist
// (because we're watching for their creation).
const path = this.toPath(file);
@ -527,8 +537,11 @@ namespace ts.server {
return this.projectService.getSourceFileLike(fileName, this);
}
private shouldEmitFile(scriptInfo: ScriptInfo) {
return scriptInfo && !scriptInfo.isDynamicOrHasMixedContent();
/*@internal*/
shouldEmitFile(scriptInfo: ScriptInfo | undefined) {
return scriptInfo &&
!scriptInfo.isDynamicOrHasMixedContent() &&
!this.program!.isSourceOfProjectReferenceRedirect(scriptInfo.path);
}
getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[] {
@ -538,7 +551,7 @@ namespace ts.server {
updateProjectIfDirty(this);
this.builderState = BuilderState.create(this.program!, this.projectService.toCanonicalFileName, this.builderState);
return mapDefined(BuilderState.getFilesAffectedBy(this.builderState, this.program!, scriptInfo.path, this.cancellationToken, data => this.projectService.host.createHash!(data)), // TODO: GH#18217
sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)!) ? sourceFile.fileName : undefined);
sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)) ? sourceFile.fileName : undefined);
}
/**
@ -1223,6 +1236,11 @@ namespace ts.server {
this.rootFilesMap.delete(info.path);
}
/*@internal*/
isSourceOfProjectReferenceRedirect(fileName: string) {
return !!this.program && this.program.isSourceOfProjectReferenceRedirect(fileName);
}
protected enableGlobalPlugins(options: CompilerOptions, pluginConfigOverrides: Map<any> | undefined) {
const host = this.projectService.host;
@ -1475,6 +1493,8 @@ namespace ts.server {
configFileWatcher: FileWatcher | undefined;
private directoriesWatchedForWildcards: Map<WildcardDirectoryWatcher> | undefined;
readonly canonicalConfigFilePath: NormalizedPath;
private projectReferenceCallbacks: ResolvedProjectReferenceCallbacks | undefined;
private mapOfDeclarationDirectories: Map<true> | undefined;
/* @internal */
pendingReload: ConfigFileProgramReloadLevel | undefined;
@ -1520,6 +1540,63 @@ namespace ts.server {
this.canonicalConfigFilePath = asNormalizedPath(projectService.toCanonicalFileName(configFileName));
}
/* @internal */
setResolvedProjectReferenceCallbacks(projectReferenceCallbacks: ResolvedProjectReferenceCallbacks) {
this.projectReferenceCallbacks = projectReferenceCallbacks;
}
/* @internal */
useSourceOfProjectReferenceRedirect = () => !!this.languageServiceEnabled &&
!this.getCompilerOptions().disableSourceOfProjectReferenceRedirect;
/**
* 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
*/
fileExists(file: string): boolean {
// Project references go to source file instead of .d.ts file
if (this.useSourceOfProjectReferenceRedirect() && this.projectReferenceCallbacks) {
const source = this.projectReferenceCallbacks.getSourceOfProjectReferenceRedirect(file);
if (source) return isString(source) ? super.fileExists(source) : true;
}
return super.fileExists(file);
}
/**
* This implementation of directoryExists checks if the directory being requested is
* directory of .d.ts file for the referenced Project.
* If it is it returns true irrespective of whether that directory exists on host
*/
directoryExists(path: string): boolean {
if (super.directoryExists(path)) return true;
if (!this.useSourceOfProjectReferenceRedirect() || !this.projectReferenceCallbacks) return false;
if (!this.mapOfDeclarationDirectories) {
this.mapOfDeclarationDirectories = createMap();
this.projectReferenceCallbacks.forEachResolvedProjectReference(ref => {
if (!ref) return;
const out = ref.commandLine.options.outFile || ref.commandLine.options.outDir;
if (out) {
this.mapOfDeclarationDirectories!.set(getDirectoryPath(this.toPath(out)), true);
}
else {
// Set declaration's in different locations only, if they are next to source the directory present doesnt change
const declarationDir = ref.commandLine.options.declarationDir || ref.commandLine.options.outDir;
if (declarationDir) {
this.mapOfDeclarationDirectories!.set(this.toPath(declarationDir), true);
}
}
});
}
const dirPath = this.toPath(path);
const dirPathWithTrailingDirectorySeparator = `${dirPath}${directorySeparator}`;
return !!forEachKey(
this.mapOfDeclarationDirectories,
declDirPath => dirPath === declDirPath || startsWith(declDirPath, dirPathWithTrailingDirectorySeparator)
);
}
/**
* If the project has reload from disk pending, it reloads (and then updates graph as part of that) instead of just updating the graph
* @returns: true if set of files in the project stays the same and false - otherwise.
@ -1528,6 +1605,8 @@ namespace ts.server {
this.isInitialLoadPending = returnFalse;
const reloadLevel = this.pendingReload;
this.pendingReload = ConfigFileProgramReloadLevel.None;
this.projectReferenceCallbacks = undefined;
this.mapOfDeclarationDirectories = undefined;
let result: boolean;
switch (reloadLevel) {
case ConfigFileProgramReloadLevel.Partial:
@ -1570,6 +1649,12 @@ namespace ts.server {
return program && program.forEachResolvedProjectReference(cb);
}
/*@internal*/
getResolvedProjectReferenceToRedirect(fileName: string): ResolvedProjectReference | undefined {
const program = this.getCurrentProgram();
return program && program.getResolvedProjectReferenceToRedirect(fileName);
}
/*@internal*/
enablePluginsWithOptions(options: CompilerOptions, pluginConfigOverrides: Map<any> | undefined) {
const host = this.projectService.host;
@ -1652,6 +1737,8 @@ namespace ts.server {
this.stopWatchingWildCards();
this.projectErrors = undefined;
this.configFileSpecs = undefined;
this.projectReferenceCallbacks = undefined;
this.mapOfDeclarationDirectories = undefined;
super.close();
}

View file

@ -495,15 +495,17 @@ namespace ts.server {
// the default project; if no configured projects, the first external project should
// be the default project; otherwise the first inferred project should be the default.
let firstExternalProject;
let firstConfiguredProject;
for (const project of this.containingProjects) {
if (project.projectKind === ProjectKind.Configured) {
return project;
if (!project.isSourceOfProjectReferenceRedirect(this.fileName)) return project;
if (!firstConfiguredProject) firstConfiguredProject = project;
}
else if (project.projectKind === ProjectKind.External && !firstExternalProject) {
firstExternalProject = project;
}
}
return firstExternalProject || this.containingProjects[0];
return firstConfiguredProject || firstExternalProject || this.containingProjects[0];
}
}

View file

@ -448,7 +448,9 @@ namespace ts.server {
function getDefinitionInProject(definition: DocumentPosition | undefined, definingProject: Project, project: Project): DocumentPosition | undefined {
if (!definition || project.containsFile(toNormalizedPath(definition.fileName))) return definition;
const mappedDefinition = definingProject.getLanguageService().getSourceMapper().tryGetGeneratedPosition(definition);
const mappedDefinition = definingProject.isSourceOfProjectReferenceRedirect(definition.fileName) ?
definition :
definingProject.getLanguageService().getSourceMapper().tryGetGeneratedPosition(definition);
return mappedDefinition && project.containsFile(toNormalizedPath(mappedDefinition.fileName)) ? mappedDefinition : undefined;
}
@ -477,7 +479,7 @@ namespace ts.server {
for (const symlinkedProject of symlinkedProjects) addToTodo({ project: symlinkedProject, location: originalLocation as TLocation }, toDo!, seenProjects);
});
}
return originalLocation;
return originalLocation === location ? undefined : originalLocation;
});
return toDo;
}
@ -1037,7 +1039,9 @@ namespace ts.server {
private getEmitOutput(args: protocol.FileRequestArgs): EmitOutput {
const { file, project } = this.getFileAndProject(args);
return project.getLanguageService().getEmitOutput(file);
return project.shouldEmitFile(project.getScriptInfo(file)) ?
project.getLanguageService().getEmitOutput(file) :
{ emitSkipped: true, outputFiles: [] };
}
private mapDefinitionInfo(definitions: readonly DefinitionInfo[], project: Project): readonly protocol.FileSpanWithContext[] {
@ -1672,10 +1676,10 @@ namespace ts.server {
}
}
private createCheckList(fileNames: string[], defaultProject?: Project): PendingErrorCheck[] {
private createCheckList(fileNames: string[]): PendingErrorCheck[] {
return mapDefined<string, PendingErrorCheck>(fileNames, uncheckedFileName => {
const fileName = toNormalizedPath(uncheckedFileName);
const project = defaultProject || this.projectService.tryGetDefaultProjectForFile(fileName);
const project = this.projectService.tryGetDefaultProjectForFile(fileName);
return project && { fileName, project };
});
}

View file

@ -1149,10 +1149,10 @@ namespace ts {
useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
getCurrentDirectory: () => currentDirectory,
getProgram,
fileExists: host.fileExists && (f => host.fileExists!(f)),
readFile: host.readFile && ((f, encoding) => host.readFile!(f, encoding)),
getDocumentPositionMapper: host.getDocumentPositionMapper && ((generatedFileName, sourceFileName) => host.getDocumentPositionMapper!(generatedFileName, sourceFileName)),
getSourceFileLike: host.getSourceFileLike && (f => host.getSourceFileLike!(f)),
fileExists: maybeBind(host, host.fileExists),
readFile: maybeBind(host, host.readFile),
getDocumentPositionMapper: maybeBind(host, host.getDocumentPositionMapper),
getSourceFileLike: maybeBind(host, host.getSourceFileLike),
log
});
@ -1250,6 +1250,12 @@ namespace ts {
if (host.resolveTypeReferenceDirectives) {
compilerHost.resolveTypeReferenceDirectives = (...args) => host.resolveTypeReferenceDirectives!(...args);
}
if (host.setResolvedProjectReferenceCallbacks) {
compilerHost.setResolvedProjectReferenceCallbacks = callbacks => host.setResolvedProjectReferenceCallbacks!(callbacks);
}
if (host.useSourceOfProjectReferenceRedirect) {
compilerHost.useSourceOfProjectReferenceRedirect = () => host.useSourceOfProjectReferenceRedirect!();
}
const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings);
const options: CreateProgramOptions = {

View file

@ -70,6 +70,11 @@ namespace ts {
if (!sourceFile) return undefined;
const program = host.getProgram()!;
// If this is source file of project reference source (instead of redirect) there is no generated position
if (program.isSourceOfProjectReferenceRedirect(sourceFile.fileName)) {
return undefined;
}
const options = program.getCompilerOptions();
const outPath = options.outFile || options.out;

View file

@ -234,6 +234,10 @@ namespace ts {
getDocumentPositionMapper?(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined;
/* @internal */
getSourceFileLike?(fileName: string): SourceFileLike | undefined;
/* @internal */
setResolvedProjectReferenceCallbacks?(callbacks: ResolvedProjectReferenceCallbacks): void;
/* @internal */
useSourceOfProjectReferenceRedirect?(): boolean;
}
/* @internal */

View file

@ -145,6 +145,8 @@
"unittests/tsserver/occurences.ts",
"unittests/tsserver/openFile.ts",
"unittests/tsserver/projectErrors.ts",
"unittests/tsserver/projectReferenceCompileOnSave.ts",
"unittests/tsserver/projectReferenceErrors.ts",
"unittests/tsserver/projectReferences.ts",
"unittests/tsserver/projects.ts",
"unittests/tsserver/refactors.ts",

View file

@ -2,18 +2,12 @@ namespace ts.tscWatch {
import projectsLocation = TestFSWithWatch.tsbuildProjectsLocation;
import getFilePathInProject = TestFSWithWatch.getTsBuildProjectFilePath;
import getFileFromProject = TestFSWithWatch.getTsBuildProjectFile;
type TsBuildWatchSystem = WatchedSystem & { writtenFiles: Map<true>; };
type TsBuildWatchSystem = TestFSWithWatch.TestServerHostTrackingWrittenFiles;
function createTsBuildWatchSystem(fileOrFolderList: readonly TestFSWithWatch.FileOrFolderOrSymLink[], params?: TestFSWithWatch.TestServerHostCreationParameters) {
const host = createWatchedSystem(fileOrFolderList, params) as TsBuildWatchSystem;
const originalWriteFile = host.writeFile;
host.writtenFiles = createMap<true>();
host.writeFile = (fileName, content) => {
originalWriteFile.call(host, fileName, content);
const path = host.toFullPath(fileName);
host.writtenFiles.set(path, true);
};
return host;
return TestFSWithWatch.changeToHostTrackingWrittenFiles(
createWatchedSystem(fileOrFolderList, params)
);
}
export function createSolutionBuilder(system: WatchedSystem, rootNames: readonly string[], defaultOptions?: BuildOptions) {
@ -710,8 +704,8 @@ let x: string = 10;`);
const coreIndexDts = projectFileName(SubProject.core, "index.d.ts");
const coreAnotherModuleDts = projectFileName(SubProject.core, "anotherModule.d.ts");
const logicIndexDts = projectFileName(SubProject.logic, "index.d.ts");
const expectedWatchedFiles = () => [core[0], logic[0], ...tests, libFile].map(f => f.path).concat([coreIndexDts, coreAnotherModuleDts, logicIndexDts].map(f => f.toLowerCase()));
const expectedWatchedDirectoriesRecursive = projectSystem.getTypeRootsFromLocation(projectPath(SubProject.tests));
const expectedProjectFiles = () => [libFile, ...tests, ...logic.slice(1), ...core.slice(1, core.length - 1)].map(f => f.path);
const expectedProgramFiles = () => [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, logicIndexDts];
function createSolutionAndWatchMode() {
@ -723,12 +717,19 @@ let x: string = 10;`);
}
function verifyWatches(host: TsBuildWatchSystem, withTsserver?: boolean) {
verifyWatchesOfProject(host, withTsserver ? expectedWatchedFiles().filter(f => f !== tests[1].path.toLowerCase()) : expectedWatchedFiles(), expectedWatchedDirectoriesRecursive);
verifyWatchesOfProject(
host,
withTsserver ?
[...core.slice(0, core.length - 1), ...logic, tests[0], libFile].map(f => f.path.toLowerCase()) :
[core[0], logic[0], ...tests, libFile].map(f => f.path).concat([coreIndexDts, coreAnotherModuleDts, logicIndexDts].map(f => f.toLowerCase())),
expectedWatchedDirectoriesRecursive
);
}
function verifyScenario(
edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder<EmitAndSemanticDiagnosticsBuilderProgram>) => void,
expectedFilesAfterEdit: () => readonly string[]
expectedProgramFilesAfterEdit: () => readonly string[],
expectedProjectFilesAfterEdit: () => readonly string[]
) {
it("with tsc-watch", () => {
const { host, solutionBuilder, watch } = createSolutionAndWatchMode();
@ -737,7 +738,7 @@ let x: string = 10;`);
host.checkTimeoutQueueLengthAndRun(1);
checkOutputErrorsIncremental(host, emptyArray);
checkProgramActualFiles(watch(), expectedFilesAfterEdit());
checkProgramActualFiles(watch(), expectedProgramFilesAfterEdit());
});
@ -747,7 +748,7 @@ let x: string = 10;`);
edit(host, solutionBuilder);
host.checkTimeoutQueueLengthAndRun(2);
checkProjectActualFiles(service, tests[0].path, [tests[0].path, ...expectedFilesAfterEdit()]);
checkProjectActualFiles(service, tests[0].path, expectedProjectFilesAfterEdit());
});
}
@ -777,7 +778,7 @@ function foo() {
// not ideal, but currently because of d.ts but no new file is written
// There will be timeout queued even though file contents are same
}, expectedProgramFiles);
}, expectedProgramFiles, expectedProjectFiles);
});
describe("non local edit in ts file, rebuilds in watch compilation", () => {
@ -787,7 +788,7 @@ export function gfoo() {
}`);
solutionBuilder.invalidateProject(logic[0].path.toLowerCase() as ResolvedConfigFilePath);
solutionBuilder.buildNextInvalidatedProject();
}, expectedProgramFiles);
}, expectedProgramFiles, expectedProjectFiles);
});
describe("change in project reference config file builds correctly", () => {
@ -798,7 +799,7 @@ export function gfoo() {
}));
solutionBuilder.invalidateProject(logic[0].path.toLowerCase() as ResolvedConfigFilePath, ConfigFileProgramReloadLevel.Full);
solutionBuilder.buildNextInvalidatedProject();
}, () => [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, projectFilePath(SubProject.logic, "decls/index.d.ts")]);
}, () => [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, projectFilePath(SubProject.logic, "decls/index.d.ts")], expectedProjectFiles);
});
});
@ -888,7 +889,9 @@ export function gfoo() {
const aDts = dtsFile(multiFolder ? "a/index" : "a"), bDts = dtsFile(multiFolder ? "b/index" : "b");
const expectedFiles = [jsFile(multiFolder ? "a/index" : "a"), aDts, jsFile(multiFolder ? "b/index" : "b"), bDts, jsFile(multiFolder ? "c/index" : "c")];
const expectedProgramFiles = [cTs.path, libFile.path, aDts, refs.path, bDts];
const expectedProjectFiles = [cTs.path, libFile.path, aTs.path, refs.path, bTs.path];
const expectedWatchedFiles = expectedProgramFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase());
const expectedProjectWatchedFiles = expectedProjectFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase());
const expectedWatchedDirectories = multiFolder ? [
getProjectPath(project).toLowerCase() // watches for directories created for resolution of b
] : emptyArray;
@ -926,22 +929,29 @@ export function gfoo() {
}
function verifyProject(host: TsBuildWatchSystem, service: projectSystem.TestProjectService, orphanInfos?: readonly string[]) {
verifyServerState(host, service, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos);
verifyServerState({ host, service, expectedProjectFiles, expectedProjectWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos });
}
function verifyServerState(
host: TsBuildWatchSystem,
service: projectSystem.TestProjectService,
expectedProgramFiles: readonly string[],
expectedWatchedFiles: readonly string[],
expectedWatchedDirectoriesRecursive: readonly string[],
orphanInfos?: readonly string[]) {
checkProjectActualFiles(service, cTsconfig.path, expectedProgramFiles.concat(cTsconfig.path));
const watchedFiles = expectedWatchedFiles.filter(f => f !== cTs.path.toLowerCase());
if (orphanInfos) {
interface VerifyServerState {
host: TsBuildWatchSystem;
service: projectSystem.TestProjectService;
expectedProjectFiles: readonly string[];
expectedProjectWatchedFiles: readonly string[];
expectedWatchedDirectoriesRecursive: readonly string[];
orphanInfos?: readonly string[];
}
function verifyServerState({ host, service, expectedProjectFiles, expectedProjectWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos }: VerifyServerState) {
checkProjectActualFiles(service, cTsconfig.path, expectedProjectFiles.concat(cTsconfig.path));
const watchedFiles = expectedProjectWatchedFiles.filter(f => f !== cTs.path.toLowerCase());
const actualOrphan = arrayFrom(mapDefinedIterator(
service.filenameToScriptInfo.values(),
v => v.containingProjects.length === 0 ? v.fileName : undefined
));
assert.equal(actualOrphan.length, orphanInfos ? orphanInfos.length : 0, `Orphans found: ${JSON.stringify(actualOrphan, /*replacer*/ undefined, " ")}`);
if (orphanInfos && orphanInfos.length) {
for (const orphan of orphanInfos) {
const info = service.getScriptInfoForPath(orphan as Path);
assert.isDefined(info);
assert.isDefined(info, `${orphan} expected to be present. Actual: ${JSON.stringify(actualOrphan, /*replacer*/ undefined, " ")}`);
assert.equal(info!.containingProjects.length, 0);
watchedFiles.push(orphan);
}
@ -949,16 +959,20 @@ export function gfoo() {
verifyWatchesOfProject(host, watchedFiles, expectedWatchedDirectoriesRecursive, expectedWatchedDirectories);
}
function verifyScenario(
edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder<EmitAndSemanticDiagnosticsBuilderProgram>) => void,
expectedEditErrors: readonly string[],
expectedProgramFiles: readonly string[],
expectedWatchedFiles: readonly string[],
expectedWatchedDirectoriesRecursive: readonly string[],
dependencies: readonly [string, readonly string[]][],
revert?: (host: TsBuildWatchSystem) => void,
orphanInfosAfterEdit?: readonly string[],
orphanInfosAfterRevert?: readonly string[]) {
interface VerifyScenario {
edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder<EmitAndSemanticDiagnosticsBuilderProgram>) => void;
expectedEditErrors: readonly string[];
expectedProgramFiles: readonly string[];
expectedProjectFiles: readonly string[];
expectedWatchedFiles: readonly string[];
expectedProjectWatchedFiles: readonly string[];
expectedWatchedDirectoriesRecursive: readonly string[];
dependencies: readonly [string, readonly string[]][];
revert?: (host: TsBuildWatchSystem) => void;
orphanInfosAfterEdit?: readonly string[];
orphanInfosAfterRevert?: readonly string[];
}
function verifyScenario({ edit, expectedEditErrors, expectedProgramFiles, expectedProjectFiles, expectedWatchedFiles, expectedProjectWatchedFiles, expectedWatchedDirectoriesRecursive, dependencies, revert, orphanInfosAfterEdit, orphanInfosAfterRevert }: VerifyScenario) {
it("with tsc-watch", () => {
const { host, solutionBuilder, watch } = createSolutionAndWatchMode();
@ -985,7 +999,7 @@ export function gfoo() {
edit(host, solutionBuilder);
host.checkTimeoutQueueLengthAndRun(2);
verifyServerState(host, service, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfosAfterEdit);
verifyServerState({ host, service, expectedProjectFiles, expectedProjectWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos: orphanInfosAfterEdit });
if (revert) {
revert(host);
@ -1010,20 +1024,21 @@ export function gfoo() {
});
describe("non local edit updates the program and watch correctly", () => {
verifyScenario(
(host, solutionBuilder) => {
verifyScenario({
edit: (host, solutionBuilder) => {
// edit
host.writeFile(bTs.path, `${bTs.content}
export function gfoo() {
}`);
solutionBuilder.invalidateProject(bTsconfig.path.toLowerCase() as ResolvedConfigFilePath);
host.writeFile(bTs.path, `${bTs.content}\nexport function gfoo() {\n}`);
solutionBuilder.invalidateProject((bTsconfig.path.toLowerCase() as ResolvedConfigFilePath));
solutionBuilder.buildNextInvalidatedProject();
},
emptyArray,
expectedEditErrors: emptyArray,
expectedProgramFiles,
expectedProjectFiles,
expectedWatchedFiles,
expectedProjectWatchedFiles,
expectedWatchedDirectoriesRecursive,
defaultDependencies);
dependencies: defaultDependencies
});
});
describe("edit on config file", () => {
@ -1032,30 +1047,32 @@ export function gfoo() {
path: getFilePathInProject(project, "nrefs/a.d.ts"),
content: refs.content
};
verifyScenario(
host => {
verifyScenario({
edit: host => {
const cTsConfigJson = JSON.parse(cTsconfig.content);
host.ensureFileOrFolder(nrefs);
cTsConfigJson.compilerOptions.paths = { "@ref/*": nrefsPath };
host.writeFile(cTsconfig.path, JSON.stringify(cTsConfigJson));
},
emptyArray,
expectedProgramFiles.map(nrefReplacer),
expectedWatchedFiles.map(nrefReplacer),
expectedWatchedDirectoriesRecursive.map(nrefReplacer),
[
expectedEditErrors: emptyArray,
expectedProgramFiles: expectedProgramFiles.map(nrefReplacer),
expectedProjectFiles: expectedProjectFiles.map(nrefReplacer),
expectedWatchedFiles: expectedWatchedFiles.map(nrefReplacer),
expectedProjectWatchedFiles: expectedProjectWatchedFiles.map(nrefReplacer),
expectedWatchedDirectoriesRecursive: expectedWatchedDirectoriesRecursive.map(nrefReplacer),
dependencies: [
[aDts, [aDts]],
[bDts, [bDts, aDts]],
[nrefs.path, [nrefs.path]],
[cTs.path, [cTs.path, nrefs.path, bDts]]
],
// revert the update
host => host.writeFile(cTsconfig.path, cTsconfig.content),
revert: host => host.writeFile(cTsconfig.path, cTsconfig.content),
// AfterEdit:: Extra watched files on server since the script infos arent deleted till next file open
[refs.path.toLowerCase()],
orphanInfosAfterEdit: [refs.path.toLowerCase()],
// AfterRevert:: Extra watched files on server since the script infos arent deleted till next file open
[nrefs.path.toLowerCase()]
);
orphanInfosAfterRevert: [nrefs.path.toLowerCase()]
});
});
describe("edit in referenced config file", () => {
@ -1064,82 +1081,84 @@ export function gfoo() {
content: "export declare class A {}"
};
const expectedProgramFiles = [cTs.path, bDts, nrefs.path, refs.path, libFile.path];
const expectedProjectFiles = [cTs.path, bTs.path, nrefs.path, refs.path, libFile.path];
const [, ...expectedWatchedDirectoriesRecursiveWithoutA] = expectedWatchedDirectoriesRecursive; // Not looking in a folder for resolution in multi folder scenario
verifyScenario(
host => {
verifyScenario({
edit: host => {
const bTsConfigJson = JSON.parse(bTsconfig.content);
host.ensureFileOrFolder(nrefs);
bTsConfigJson.compilerOptions.paths = { "@ref/*": nrefsPath };
host.writeFile(bTsconfig.path, JSON.stringify(bTsConfigJson));
},
emptyArray,
expectedEditErrors: emptyArray,
expectedProgramFiles,
expectedProgramFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase()),
(multiFolder ? expectedWatchedDirectoriesRecursiveWithoutA : expectedWatchedDirectoriesRecursive).concat(getFilePathInProject(project, "nrefs").toLowerCase()),
[
expectedProjectFiles,
expectedWatchedFiles: expectedProgramFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase()),
expectedProjectWatchedFiles: expectedProjectFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase()),
expectedWatchedDirectoriesRecursive: (multiFolder ? expectedWatchedDirectoriesRecursiveWithoutA : expectedWatchedDirectoriesRecursive).concat(getFilePathInProject(project, "nrefs").toLowerCase()),
dependencies: [
[nrefs.path, [nrefs.path]],
[bDts, [bDts, nrefs.path]],
[refs.path, [refs.path]],
[cTs.path, [cTs.path, refs.path, bDts]],
],
// revert the update
host => host.writeFile(bTsconfig.path, bTsconfig.content),
revert: host => host.writeFile(bTsconfig.path, bTsconfig.content),
// AfterEdit:: Extra watched files on server since the script infos arent deleted till next file open
[aDts.toLowerCase()],
orphanInfosAfterEdit: [aTs.path.toLowerCase()],
// AfterRevert:: Extra watched files on server since the script infos arent deleted till next file open
[nrefs.path.toLowerCase()]
);
orphanInfosAfterRevert: [nrefs.path.toLowerCase()]
});
});
describe("deleting referenced config file", () => {
const expectedProgramFiles = [cTs.path, bTs.path, refs.path, libFile.path];
const expectedWatchedFiles = expectedProgramFiles.concat(cTsconfig.path, bTsconfig.path).map(s => s.toLowerCase());
const [, ...expectedWatchedDirectoriesRecursiveWithoutA] = expectedWatchedDirectoriesRecursive; // Not looking in a folder for resolution in multi folder scenario
// Resolutions should change now
// Should map to b.ts instead with options from our own config
verifyScenario(
host => host.deleteFile(bTsconfig.path),
[
verifyScenario({
edit: host => host.deleteFile(bTsconfig.path),
expectedEditErrors: [
`${multiFolder ? "c/tsconfig.json" : "tsconfig.c.json"}(9,21): error TS6053: File '/user/username/projects/transitiveReferences/${multiFolder ? "b" : "tsconfig.b.json"}' not found.\n`
],
expectedProgramFiles,
expectedProgramFiles.concat(cTsconfig.path, bTsconfig.path).map(s => s.toLowerCase()),
multiFolder ? expectedWatchedDirectoriesRecursiveWithoutA : expectedWatchedDirectoriesRecursive,
[
expectedProjectFiles: expectedProgramFiles,
expectedWatchedFiles,
expectedProjectWatchedFiles: expectedWatchedFiles,
expectedWatchedDirectoriesRecursive: multiFolder ? expectedWatchedDirectoriesRecursiveWithoutA : expectedWatchedDirectoriesRecursive,
dependencies: [
[bTs.path, [bTs.path, refs.path]],
[refs.path, [refs.path]],
[cTs.path, [cTs.path, refs.path, bTs.path]],
],
// revert the update
host => host.writeFile(bTsconfig.path, bTsconfig.content),
revert: host => host.writeFile(bTsconfig.path, bTsconfig.content),
// AfterEdit:: Extra watched files on server since the script infos arent deleted till next file open
[bDts.toLowerCase(), aDts.toLowerCase(), aTsconfig.path.toLowerCase()],
// AfterRevert:: Extra watched files on server since the script infos arent deleted till next file open
[bTs.path.toLowerCase()]
);
orphanInfosAfterEdit: [aTs.path.toLowerCase(), aTsconfig.path.toLowerCase()],
});
});
describe("deleting transitively referenced config file", () => {
verifyScenario(
host => host.deleteFile(aTsconfig.path),
[
verifyScenario({
edit: host => host.deleteFile(aTsconfig.path),
expectedEditErrors: [
`${multiFolder ? "b/tsconfig.json" : "tsconfig.b.json"}(10,21): error TS6053: File '/user/username/projects/transitiveReferences/${multiFolder ? "a" : "tsconfig.a.json"}' not found.\n`
],
expectedProgramFiles.map(s => s.replace(aDts, aTs.path)),
expectedWatchedFiles.map(s => s.replace(aDts.toLowerCase(), aTs.path.toLocaleLowerCase())),
expectedProgramFiles: expectedProgramFiles.map(s => s.replace(aDts, aTs.path)),
expectedProjectFiles,
expectedWatchedFiles: expectedWatchedFiles.map(s => s.replace(aDts.toLowerCase(), aTs.path.toLocaleLowerCase())),
expectedProjectWatchedFiles,
expectedWatchedDirectoriesRecursive,
[
dependencies: [
[aTs.path, [aTs.path]],
[bDts, [bDts, aTs.path]],
[refs.path, [refs.path]],
[cTs.path, [cTs.path, refs.path, bDts]],
],
// revert the update
host => host.writeFile(aTsconfig.path, aTsconfig.content),
// AfterEdit:: Extra watched files on server since the script infos arent deleted till next file open
[aDts.toLowerCase()],
// AfterRevert:: Extra watched files on server since the script infos arent deleted till next file open
[aTs.path.toLowerCase()]
);
revert: host => host.writeFile(aTsconfig.path, aTsconfig.content),
});
});
}

View file

@ -179,7 +179,7 @@ namespace ts.projectSystem {
}
function verifyUserTsConfigProject(session: TestSession) {
checkProjectActualFiles(session.getProjectService().configuredProjects.get(userTsconfig.path)!, [userTs.path, aDts.path, userTsconfig.path]);
checkProjectActualFiles(session.getProjectService().configuredProjects.get(userTsconfig.path)!, [userTs.path, aTs.path, userTsconfig.path]);
}
it("goToDefinition", () => {
@ -450,6 +450,13 @@ namespace ts.projectSystem {
name: "function f(): void",
},
references: [
makeReferenceEntry({
file: aTs,
text: "f",
options: { index: 1 },
contextText: "function f() {}",
isDefinition: true
}),
{
fileName: bTs.path,
isDefinition: false,
@ -457,13 +464,6 @@ namespace ts.projectSystem {
isWriteAccess: false,
textSpan: { start: 0, length: 1 },
},
makeReferenceEntry({
file: aTs,
text: "f",
options: { index: 1 },
contextText: "function f() {}",
isDefinition: true
})
],
}
]);

View file

@ -73,44 +73,64 @@ namespace ts.projectSystem {
verifyEvent(project, `Change in config file detected`);
});
it("when opening original location project", () => {
const aDTs: File = {
path: `${projectRoot}/a/a.d.ts`,
content: `export declare class A {
describe("when opening original location project", () => {
it("with project references", () => {
verify();
});
it("when disableSourceOfProjectReferenceRedirect is true", () => {
verify(/*disableSourceOfProjectReferenceRedirect*/ true);
});
function verify(disableSourceOfProjectReferenceRedirect?: true) {
const aDTs: File = {
path: `${projectRoot}/a/a.d.ts`,
content: `export declare class A {
}
//# sourceMappingURL=a.d.ts.map
`
};
const aDTsMap: File = {
path: `${projectRoot}/a/a.d.ts.map`,
content: `{"version":3,"file":"a.d.ts","sourceRoot":"","sources":["./a.ts"],"names":[],"mappings":"AAAA,qBAAa,CAAC;CAAI"}`
};
const bTs: File = {
path: bTsPath,
content: `import {A} from "../a/a"; new A();`
};
const configB: File = {
path: configBPath,
content: JSON.stringify({
references: [{ path: "../a" }]
})
};
};
const aDTsMap: File = {
path: `${projectRoot}/a/a.d.ts.map`,
content: `{"version":3,"file":"a.d.ts","sourceRoot":"","sources":["./a.ts"],"names":[],"mappings":"AAAA,qBAAa,CAAC;CAAI"}`
};
const bTs: File = {
path: bTsPath,
content: `import {A} from "../a/a"; new A();`
};
const configB: File = {
path: configBPath,
content: JSON.stringify({
...(disableSourceOfProjectReferenceRedirect && {
compilerOptions: {
disableSourceOfProjectReferenceRedirect
}
}),
references: [{ path: "../a" }]
})
};
const { service, session, verifyEventWithOpenTs, verifyEvent } = createSessionToVerifyEvent(files.concat(aDTs, aDTsMap, bTs, configB));
verifyEventWithOpenTs(bTs, configB.path, 1);
const { service, session, verifyEventWithOpenTs, verifyEvent } = createSessionToVerifyEvent(files.concat(aDTs, aDTsMap, bTs, configB));
verifyEventWithOpenTs(bTs, configB.path, 1);
session.executeCommandSeq<protocol.ReferencesRequest>({
command: protocol.CommandTypes.References,
arguments: {
file: bTs.path,
...protocolLocationFromSubstring(bTs.content, "A()")
}
});
session.executeCommandSeq<protocol.ReferencesRequest>({
command: protocol.CommandTypes.References,
arguments: {
file: bTs.path,
...protocolLocationFromSubstring(bTs.content, "A()")
}
});
checkNumberOfProjects(service, { configuredProjects: 2 });
const project = service.configuredProjects.get(configA.path)!;
assert.isDefined(project);
verifyEvent(project, `Creating project for original file: ${aTs.path} for location: ${aDTs.path}`);
checkNumberOfProjects(service, { configuredProjects: 2 });
const project = service.configuredProjects.get(configA.path)!;
assert.isDefined(project);
verifyEvent(
project,
disableSourceOfProjectReferenceRedirect ?
`Creating project for original file: ${aTs.path} for location: ${aDTs.path}` :
`Creating project for original file: ${aTs.path}`
);
}
});
describe("with external projects and config files ", () => {

View file

@ -491,8 +491,8 @@ namespace ts.projectSystem {
checkArray("Open files", arrayFrom(projectService.openFiles.keys(), path => projectService.getScriptInfoForPath(path as Path)!.fileName), expectedFiles.map(file => file.path));
}
export function checkScriptInfos(projectService: server.ProjectService, expectedFiles: readonly string[]) {
checkArray("ScriptInfos files", arrayFrom(projectService.filenameToScriptInfo.values(), info => info.fileName), expectedFiles);
export function checkScriptInfos(projectService: server.ProjectService, expectedFiles: readonly string[], additionInfo?: string) {
checkArray(`ScriptInfos files: ${additionInfo || ""}`, arrayFrom(projectService.filenameToScriptInfo.values(), info => info.fileName), expectedFiles);
}
export function protocolLocationFromSubstring(str: string, substring: string): protocol.Location {
@ -501,7 +501,7 @@ namespace ts.projectSystem {
return protocolToLocation(str)(start);
}
function protocolToLocation(text: string): (pos: number) => protocol.Location {
export function protocolToLocation(text: string): (pos: number) => protocol.Location {
const lineStarts = computeLineStarts(text);
return pos => {
const x = computeLineAndCharacterOfPosition(lineStarts, pos);

View file

@ -0,0 +1,410 @@
namespace ts.projectSystem {
describe("unittests:: tsserver:: with project references and compile on save", () => {
const projectLocation = "/user/username/projects/myproject";
const dependecyLocation = `${projectLocation}/dependency`;
const usageLocation = `${projectLocation}/usage`;
const dependencyTs: File = {
path: `${dependecyLocation}/fns.ts`,
content: `export function fn1() { }
export function fn2() { }
`
};
const dependencyConfig: File = {
path: `${dependecyLocation}/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { composite: true, declarationDir: "../decls" },
compileOnSave: true
})
};
const usageTs: File = {
path: `${usageLocation}/usage.ts`,
content: `import {
fn1,
fn2,
} from '../decls/fns'
fn1();
fn2();
`
};
const usageConfig: File = {
path: `${usageLocation}/tsconfig.json`,
content: JSON.stringify({
compileOnSave: true,
references: [{ path: "../dependency" }]
})
};
interface VerifySingleScenarioWorker extends VerifySingleScenario {
withProject: boolean;
}
function verifySingleScenarioWorker({
withProject, scenario, openFiles, requestArgs, change, expectedResult
}: VerifySingleScenarioWorker) {
it(scenario, () => {
const host = TestFSWithWatch.changeToHostTrackingWrittenFiles(
createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile])
);
const session = createSession(host);
openFilesForSession(openFiles(), session);
const reqArgs = requestArgs();
const {
expectedAffected,
expectedEmit: { expectedEmitSuccess, expectedFiles },
expectedEmitOutput
} = expectedResult(withProject);
if (change) {
session.executeCommandSeq<protocol.CompileOnSaveAffectedFileListRequest>({
command: protocol.CommandTypes.CompileOnSaveAffectedFileList,
arguments: { file: dependencyTs.path }
});
const { file, insertString } = change();
if (session.getProjectService().openFiles.has(file.path)) {
const toLocation = protocolToLocation(file.content);
const location = toLocation(file.content.length);
session.executeCommandSeq<protocol.ChangeRequest>({
command: protocol.CommandTypes.Change,
arguments: {
file: file.path,
...location,
endLine: location.line,
endOffset: location.offset,
insertString
}
});
}
else {
host.writeFile(file.path, `${file.content}${insertString}`);
}
host.writtenFiles.clear();
}
const args = withProject ? reqArgs : { file: reqArgs.file };
// Verify CompileOnSaveAffectedFileList
const actualAffectedFiles = session.executeCommandSeq<protocol.CompileOnSaveAffectedFileListRequest>({
command: protocol.CommandTypes.CompileOnSaveAffectedFileList,
arguments: args
}).response as protocol.CompileOnSaveAffectedFileListSingleProject[];
assert.deepEqual(actualAffectedFiles, expectedAffected, "Affected files");
// Verify CompileOnSaveEmit
const actualEmit = session.executeCommandSeq<protocol.CompileOnSaveEmitFileRequest>({
command: protocol.CommandTypes.CompileOnSaveEmitFile,
arguments: args
}).response;
assert.deepEqual(actualEmit, expectedEmitSuccess, "Emit files");
assert.equal(host.writtenFiles.size, expectedFiles.length);
for (const file of expectedFiles) {
assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`);
assert.isTrue(host.writtenFiles.has(file.path), `${file.path} is newly written`);
}
// Verify EmitOutput
const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq<protocol.EmitOutputRequest>({
command: protocol.CommandTypes.EmitOutput,
arguments: args
}).response as EmitOutput;
assert.deepEqual(actualEmitOutput, expectedEmitOutput, "Emit output");
});
}
interface VerifySingleScenario {
scenario: string;
openFiles: () => readonly File[];
requestArgs: () => protocol.FileRequestArgs;
skipWithoutProject?: boolean;
change?: () => SingleScenarioChange;
expectedResult: GetSingleScenarioResult;
}
function verifySingleScenario(scenario: VerifySingleScenario) {
if (!scenario.skipWithoutProject) {
describe("without specifying project file", () => {
verifySingleScenarioWorker({
withProject: false,
...scenario
});
});
}
describe("with specifying project file", () => {
verifySingleScenarioWorker({
withProject: true,
...scenario
});
});
}
interface SingleScenarioExpectedEmit {
expectedEmitSuccess: boolean;
expectedFiles: readonly File[];
}
interface SingleScenarioResult {
expectedAffected: protocol.CompileOnSaveAffectedFileListSingleProject[];
expectedEmit: SingleScenarioExpectedEmit;
expectedEmitOutput: EmitOutput;
}
type GetSingleScenarioResult = (withProject: boolean) => SingleScenarioResult;
interface SingleScenarioChange {
file: File;
insertString: string;
}
interface ScenarioDetails {
scenarioName: string;
requestArgs: () => protocol.FileRequestArgs;
skipWithoutProject?: boolean;
initial: GetSingleScenarioResult;
localChangeToDependency: GetSingleScenarioResult;
localChangeToUsage: GetSingleScenarioResult;
changeToDependency: GetSingleScenarioResult;
changeToUsage: GetSingleScenarioResult;
}
interface VerifyScenario {
openFiles: () => readonly File[];
scenarios: readonly ScenarioDetails[];
}
const localChange = "function fn3() { }";
const change = `export ${localChange}`;
const changeJs = `function fn3() { }
exports.fn3 = fn3;`;
const changeDts = "export declare function fn3(): void;";
function verifyScenario({ openFiles, scenarios }: VerifyScenario) {
for (const {
scenarioName, requestArgs, skipWithoutProject, initial,
localChangeToDependency, localChangeToUsage,
changeToDependency, changeToUsage
} of scenarios) {
describe(scenarioName, () => {
verifySingleScenario({
scenario: "with initial file open",
openFiles,
requestArgs,
skipWithoutProject,
expectedResult: initial
});
verifySingleScenario({
scenario: "with local change to dependency",
openFiles,
requestArgs,
skipWithoutProject,
change: () => ({ file: dependencyTs, insertString: localChange }),
expectedResult: localChangeToDependency
});
verifySingleScenario({
scenario: "with local change to usage",
openFiles,
requestArgs,
skipWithoutProject,
change: () => ({ file: usageTs, insertString: localChange }),
expectedResult: localChangeToUsage
});
verifySingleScenario({
scenario: "with change to dependency",
openFiles,
requestArgs,
skipWithoutProject,
change: () => ({ file: dependencyTs, insertString: change }),
expectedResult: changeToDependency
});
verifySingleScenario({
scenario: "with change to usage",
openFiles,
requestArgs,
skipWithoutProject,
change: () => ({ file: usageTs, insertString: change }),
expectedResult: changeToUsage
});
});
}
}
function expectedAffectedFiles(config: File, fileNames: File[]): protocol.CompileOnSaveAffectedFileListSingleProject {
return {
projectFileName: config.path,
fileNames: fileNames.map(f => f.path),
projectUsesOutFile: false
};
}
function expectedUsageEmit(appendJsText?: string): SingleScenarioExpectedEmit {
const appendJs = appendJsText ? `${appendJsText}
` : "";
return {
expectedEmitSuccess: true,
expectedFiles: [{
path: `${usageLocation}/usage.js`,
content: `"use strict";
exports.__esModule = true;
var fns_1 = require("../decls/fns");
fns_1.fn1();
fns_1.fn2();
${appendJs}`
}]
};
}
function expectedEmitOutput({ expectedFiles }: SingleScenarioExpectedEmit): EmitOutput {
return {
outputFiles: expectedFiles.map(({ path, content }) => ({
name: path,
text: content,
writeByteOrderMark: false
})),
emitSkipped: false
};
}
function expectedUsageEmitOutput(appendJsText?: string): EmitOutput {
return expectedEmitOutput(expectedUsageEmit(appendJsText));
}
function noEmit(): SingleScenarioExpectedEmit {
return {
expectedEmitSuccess: false,
expectedFiles: emptyArray
};
}
function noEmitOutput(): EmitOutput {
return {
emitSkipped: true,
outputFiles: []
};
}
function expectedDependencyEmit(appendJsText?: string, appendDtsText?: string): SingleScenarioExpectedEmit {
const appendJs = appendJsText ? `${appendJsText}
` : "";
const appendDts = appendDtsText ? `${appendDtsText}
` : "";
return {
expectedEmitSuccess: true,
expectedFiles: [
{
path: `${dependecyLocation}/fns.js`,
content: `"use strict";
exports.__esModule = true;
function fn1() { }
exports.fn1 = fn1;
function fn2() { }
exports.fn2 = fn2;
${appendJs}`
},
{
path: `${projectLocation}/decls/fns.d.ts`,
content: `export declare function fn1(): void;
export declare function fn2(): void;
${appendDts}`
}
]
};
}
function expectedDependencyEmitOutput(appendJsText?: string, appendDtsText?: string): EmitOutput {
return expectedEmitOutput(expectedDependencyEmit(appendJsText, appendDtsText));
}
function scenarioDetailsOfUsage(isDependencyOpen?: boolean): ScenarioDetails[] {
return [
{
scenarioName: "Of usageTs",
requestArgs: () => ({ file: usageTs.path, projectFileName: usageConfig.path }),
initial: () => initialUsageTs(),
// no change to usage so same as initial only usage file
localChangeToDependency: () => initialUsageTs(),
localChangeToUsage: () => initialUsageTs(localChange),
changeToDependency: () => initialUsageTs(),
changeToUsage: () => initialUsageTs(changeJs)
},
{
scenarioName: "Of dependencyTs in usage project",
requestArgs: () => ({ file: dependencyTs.path, projectFileName: usageConfig.path }),
skipWithoutProject: !!isDependencyOpen,
initial: () => initialDependencyTs(),
localChangeToDependency: () => initialDependencyTs(/*noUsageFiles*/ true),
localChangeToUsage: () => initialDependencyTs(/*noUsageFiles*/ true),
changeToDependency: () => initialDependencyTs(),
changeToUsage: () => initialDependencyTs(/*noUsageFiles*/ true)
}
];
function initialUsageTs(jsText?: string) {
return {
expectedAffected: [
expectedAffectedFiles(usageConfig, [usageTs])
],
expectedEmit: expectedUsageEmit(jsText),
expectedEmitOutput: expectedUsageEmitOutput(jsText)
};
}
function initialDependencyTs(noUsageFiles?: true) {
return {
expectedAffected: [
expectedAffectedFiles(usageConfig, noUsageFiles ? [] : [usageTs])
],
expectedEmit: noEmit(),
expectedEmitOutput: noEmitOutput()
};
}
}
function scenarioDetailsOfDependencyWhenOpen(): ScenarioDetails {
return {
scenarioName: "Of dependencyTs",
requestArgs: () => ({ file: dependencyTs.path, projectFileName: dependencyConfig.path }),
initial,
localChangeToDependency: withProject => ({
expectedAffected: withProject ?
[
expectedAffectedFiles(dependencyConfig, [dependencyTs])
] :
[
expectedAffectedFiles(usageConfig, []),
expectedAffectedFiles(dependencyConfig, [dependencyTs])
],
expectedEmit: expectedDependencyEmit(localChange),
expectedEmitOutput: expectedDependencyEmitOutput(localChange)
}),
localChangeToUsage: withProject => initial(withProject, /*noUsageFiles*/ true),
changeToDependency: withProject => initial(withProject, /*noUsageFiles*/ undefined, changeJs, changeDts),
changeToUsage: withProject => initial(withProject, /*noUsageFiles*/ true)
};
function initial(withProject: boolean, noUsageFiles?: true, appendJs?: string, appendDts?: string): SingleScenarioResult {
return {
expectedAffected: withProject ?
[
expectedAffectedFiles(dependencyConfig, [dependencyTs])
] :
[
expectedAffectedFiles(usageConfig, noUsageFiles ? [] : [usageTs]),
expectedAffectedFiles(dependencyConfig, [dependencyTs])
],
expectedEmit: expectedDependencyEmit(appendJs, appendDts),
expectedEmitOutput: expectedDependencyEmitOutput(appendJs, appendDts)
};
}
}
describe("when dependency project is not open", () => {
verifyScenario({
openFiles: () => [usageTs],
scenarios: scenarioDetailsOfUsage()
});
});
describe("when the depedency file is open", () => {
verifyScenario({
openFiles: () => [usageTs, dependencyTs],
scenarios: [
...scenarioDetailsOfUsage(/*isDependencyOpen*/ true),
scenarioDetailsOfDependencyWhenOpen(),
]
});
});
});
}

View file

@ -0,0 +1,430 @@
namespace ts.projectSystem {
describe("unittests:: tsserver:: with project references and error reporting", () => {
const projectLocation = "/user/username/projects/myproject";
const dependecyLocation = `${projectLocation}/dependency`;
const usageLocation = `${projectLocation}/usage`;
interface CheckErrorsInFile {
session: TestSession;
host: TestServerHost;
expected: GetErrDiagnostics;
expectedSequenceId?: number;
}
function checkErrorsInFile({ session, host, expected: { file, syntax, semantic, suggestion }, expectedSequenceId }: CheckErrorsInFile) {
host.checkTimeoutQueueLengthAndRun(1);
checkErrorMessage(session, "syntaxDiag", { file: file.path, diagnostics: syntax });
session.clearMessages();
host.runQueuedImmediateCallbacks(1);
checkErrorMessage(session, "semanticDiag", { file: file.path, diagnostics: semantic });
session.clearMessages();
host.runQueuedImmediateCallbacks(1);
checkErrorMessage(session, "suggestionDiag", { file: file.path, diagnostics: suggestion });
if (expectedSequenceId !== undefined) {
checkCompleteEvent(session, 2, expectedSequenceId);
}
session.clearMessages();
}
interface CheckAllErrors {
session: TestSession;
host: TestServerHost;
expected: readonly GetErrDiagnostics[];
expectedSequenceId: number;
}
function checkAllErrors({ session, host, expected, expectedSequenceId }: CheckAllErrors) {
for (let i = 0; i < expected.length; i++) {
checkErrorsInFile({
session,
host,
expected: expected[i],
expectedSequenceId: i === expected.length - 1 ? expectedSequenceId : undefined
});
}
}
function verifyErrorsUsingGeterr({ allFiles, openFiles, expectedGetErr }: VerifyScenario) {
it("verifies the errors in open file", () => {
const host = createServerHost([...allFiles(), libFile]);
const session = createSession(host, { canUseEvents: true, });
openFilesForSession(openFiles(), session);
session.clearMessages();
const expectedSequenceId = session.getNextSeq();
const expected = expectedGetErr();
session.executeCommandSeq<protocol.GeterrRequest>({
command: protocol.CommandTypes.Geterr,
arguments: {
delay: 0,
files: expected.map(f => f.file.path)
}
});
checkAllErrors({ session, host, expected, expectedSequenceId });
});
}
function verifyErrorsUsingGeterrForProject({ allFiles, openFiles, expectedGetErrForProject }: VerifyScenario) {
it("verifies the errors in projects", () => {
const host = createServerHost([...allFiles(), libFile]);
const session = createSession(host, { canUseEvents: true, });
openFilesForSession(openFiles(), session);
session.clearMessages();
for (const expected of expectedGetErrForProject()) {
const expectedSequenceId = session.getNextSeq();
session.executeCommandSeq<protocol.GeterrForProjectRequest>({
command: protocol.CommandTypes.GeterrForProject,
arguments: {
delay: 0,
file: expected.project
}
});
checkAllErrors({ session, host, expected: expected.errors, expectedSequenceId });
}
});
}
function verifyErrorsUsingSyncMethods({ allFiles, openFiles, expectedSyncDiagnostics }: VerifyScenario) {
it("verifies the errors using sync commands", () => {
const host = createServerHost([...allFiles(), libFile]);
const session = createSession(host);
openFilesForSession(openFiles(), session);
for (const { file, project, syntax, semantic, suggestion } of expectedSyncDiagnostics()) {
const actualSyntax = session.executeCommandSeq<protocol.SyntacticDiagnosticsSyncRequest>({
command: protocol.CommandTypes.SyntacticDiagnosticsSync,
arguments: {
file: file.path,
projectFileName: project
}
}).response as protocol.Diagnostic[];
assert.deepEqual(actualSyntax, syntax, `Syntax diagnostics for file: ${file.path}, project: ${project}`);
const actualSemantic = session.executeCommandSeq<protocol.SemanticDiagnosticsSyncRequest>({
command: protocol.CommandTypes.SemanticDiagnosticsSync,
arguments: {
file: file.path,
projectFileName: project
}
}).response as protocol.Diagnostic[];
assert.deepEqual(actualSemantic, semantic, `Semantic diagnostics for file: ${file.path}, project: ${project}`);
const actualSuggestion = session.executeCommandSeq<protocol.SuggestionDiagnosticsSyncRequest>({
command: protocol.CommandTypes.SuggestionDiagnosticsSync,
arguments: {
file: file.path,
projectFileName: project
}
}).response as protocol.Diagnostic[];
assert.deepEqual(actualSuggestion, suggestion, `Suggestion diagnostics for file: ${file.path}, project: ${project}`);
}
});
}
function verifyConfigFileErrors({ allFiles, openFiles, expectedConfigFileDiagEvents }: VerifyScenario) {
it("verify config file errors", () => {
const host = createServerHost([...allFiles(), libFile]);
const { session, events } = createSessionWithEventTracking<server.ConfigFileDiagEvent>(host, server.ConfigFileDiagEvent);
for (const file of openFiles()) {
session.executeCommandSeq<protocol.OpenRequest>({
command: protocol.CommandTypes.Open,
arguments: { file: file.path }
});
}
assert.deepEqual(events, expectedConfigFileDiagEvents().map(data => ({
eventName: server.ConfigFileDiagEvent,
data
})));
});
}
interface GetErrDiagnostics {
file: File;
syntax: protocol.Diagnostic[];
semantic: protocol.Diagnostic[];
suggestion: protocol.Diagnostic[];
}
interface GetErrForProjectDiagnostics {
project: string;
errors: readonly GetErrDiagnostics[];
}
interface SyncDiagnostics extends GetErrDiagnostics {
project?: string;
}
interface VerifyScenario {
allFiles: () => readonly File[];
openFiles: () => readonly File[];
expectedGetErr: () => readonly GetErrDiagnostics[];
expectedGetErrForProject: () => readonly GetErrForProjectDiagnostics[];
expectedSyncDiagnostics: () => readonly SyncDiagnostics[];
expectedConfigFileDiagEvents: () => readonly server.ConfigFileDiagEvent["data"][];
}
function verifyScenario(scenario: VerifyScenario) {
verifyErrorsUsingGeterr(scenario);
verifyErrorsUsingGeterrForProject(scenario);
verifyErrorsUsingSyncMethods(scenario);
verifyConfigFileErrors(scenario);
}
function emptyDiagnostics(file: File): GetErrDiagnostics {
return {
file,
syntax: emptyArray,
semantic: emptyArray,
suggestion: emptyArray
};
}
function syncDiagnostics(diagnostics: GetErrDiagnostics, project: string): SyncDiagnostics {
return { project, ...diagnostics };
}
interface VerifyUsageAndDependency {
allFiles: readonly [File, File, File, File]; // dependencyTs, dependencyConfig, usageTs, usageConfig
usageDiagnostics(): GetErrDiagnostics;
dependencyDiagnostics(): GetErrDiagnostics;
}
function verifyUsageAndDependency({ allFiles, usageDiagnostics, dependencyDiagnostics }: VerifyUsageAndDependency) {
const [dependencyTs, dependencyConfig, usageTs, usageConfig] = allFiles;
function usageProjectDiagnostics(): GetErrForProjectDiagnostics {
return {
project: usageTs.path,
errors: [
usageDiagnostics(),
emptyDiagnostics(dependencyTs)
]
};
}
function dependencyProjectDiagnostics(): GetErrForProjectDiagnostics {
return {
project: dependencyTs.path,
errors: [
dependencyDiagnostics()
]
};
}
function usageConfigDiag(): server.ConfigFileDiagEvent["data"] {
return {
triggerFile: usageTs.path,
configFileName: usageConfig.path,
diagnostics: emptyArray
};
}
function dependencyConfigDiag(): server.ConfigFileDiagEvent["data"] {
return {
triggerFile: dependencyTs.path,
configFileName: dependencyConfig.path,
diagnostics: emptyArray
};
}
describe("when dependency project is not open", () => {
verifyScenario({
allFiles: () => allFiles,
openFiles: () => [usageTs],
expectedGetErr: () => [
usageDiagnostics()
],
expectedGetErrForProject: () => [
usageProjectDiagnostics(),
{
project: dependencyTs.path,
errors: [
emptyDiagnostics(dependencyTs),
usageDiagnostics()
]
}
],
expectedSyncDiagnostics: () => [
// Without project
usageDiagnostics(),
emptyDiagnostics(dependencyTs),
// With project
syncDiagnostics(usageDiagnostics(), usageConfig.path),
syncDiagnostics(emptyDiagnostics(dependencyTs), usageConfig.path),
],
expectedConfigFileDiagEvents: () => [
usageConfigDiag()
],
});
});
describe("when the depedency file is open", () => {
verifyScenario({
allFiles: () => allFiles,
openFiles: () => [usageTs, dependencyTs],
expectedGetErr: () => [
usageDiagnostics(),
dependencyDiagnostics(),
],
expectedGetErrForProject: () => [
usageProjectDiagnostics(),
dependencyProjectDiagnostics()
],
expectedSyncDiagnostics: () => [
// Without project
usageDiagnostics(),
dependencyDiagnostics(),
// With project
syncDiagnostics(usageDiagnostics(), usageConfig.path),
syncDiagnostics(emptyDiagnostics(dependencyTs), usageConfig.path),
syncDiagnostics(dependencyDiagnostics(), dependencyConfig.path),
],
expectedConfigFileDiagEvents: () => [
usageConfigDiag(),
dependencyConfigDiag()
],
});
});
}
describe("with module scenario", () => {
const dependencyTs: File = {
path: `${dependecyLocation}/fns.ts`,
content: `export function fn1() { }
export function fn2() { }
// Introduce error for fnErr import in main
// export function fnErr() { }
// Error in dependency ts file
export let x: string = 10;`
};
const dependencyConfig: File = {
path: `${dependecyLocation}/tsconfig.json`,
content: JSON.stringify({ compilerOptions: { composite: true, declarationDir: "../decls" } })
};
const usageTs: File = {
path: `${usageLocation}/usage.ts`,
content: `import {
fn1,
fn2,
fnErr
} from '../decls/fns'
fn1();
fn2();
fnErr();
`
};
const usageConfig: File = {
path: `${usageLocation}/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { composite: true },
references: [{ path: "../dependency" }]
})
};
function usageDiagnostics(): GetErrDiagnostics {
return {
file: usageTs,
syntax: emptyArray,
semantic: [
createDiagnostic(
{ line: 4, offset: 5 },
{ line: 4, offset: 10 },
Diagnostics.Module_0_has_no_exported_member_1,
[`"../dependency/fns"`, "fnErr"],
"error",
)
],
suggestion: emptyArray
};
}
function dependencyDiagnostics(): GetErrDiagnostics {
return {
file: dependencyTs,
syntax: emptyArray,
semantic: [
createDiagnostic(
{ line: 6, offset: 12 },
{ line: 6, offset: 13 },
Diagnostics.Type_0_is_not_assignable_to_type_1,
["10", "string"],
"error",
)
],
suggestion: emptyArray
};
}
verifyUsageAndDependency({
allFiles: [dependencyTs, dependencyConfig, usageTs, usageConfig],
usageDiagnostics,
dependencyDiagnostics
});
});
describe("with non module --out", () => {
const dependencyTs: File = {
path: `${dependecyLocation}/fns.ts`,
content: `function fn1() { }
function fn2() { }
// Introduce error for fnErr import in main
// function fnErr() { }
// Error in dependency ts file
let x: string = 10;`
};
const dependencyConfig: File = {
path: `${dependecyLocation}/tsconfig.json`,
content: JSON.stringify({ compilerOptions: { composite: true, outFile: "../dependency.js" } })
};
const usageTs: File = {
path: `${usageLocation}/usage.ts`,
content: `fn1();
fn2();
fnErr();
`
};
const usageConfig: File = {
path: `${usageLocation}/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { composite: true, outFile: "../usage.js" },
references: [{ path: "../dependency" }]
})
};
function usageDiagnostics(): GetErrDiagnostics {
return {
file: usageTs,
syntax: emptyArray,
semantic: [
createDiagnostic(
{ line: 3, offset: 1 },
{ line: 3, offset: 6 },
Diagnostics.Cannot_find_name_0,
["fnErr"],
"error",
)
],
suggestion: emptyArray
};
}
function dependencyDiagnostics(): GetErrDiagnostics {
return {
file: dependencyTs,
syntax: emptyArray,
semantic: [
createDiagnostic(
{ line: 6, offset: 5 },
{ line: 6, offset: 6 },
Diagnostics.Type_0_is_not_assignable_to_type_1,
["10", "string"],
"error",
)
],
suggestion: emptyArray
};
}
verifyUsageAndDependency({
allFiles: [dependencyTs, dependencyConfig, usageTs, usageConfig],
usageDiagnostics,
dependencyDiagnostics
});
});
});
}

File diff suppressed because it is too large Load diff

View file

@ -2567,6 +2567,7 @@ declare namespace ts {
emitDeclarationOnly?: boolean;
declarationDir?: string;
disableSizeLimit?: boolean;
disableSourceOfProjectReferenceRedirect?: boolean;
downlevelIteration?: boolean;
emitBOM?: boolean;
emitDecoratorMetadata?: boolean;
@ -8529,7 +8530,6 @@ declare namespace ts.server {
getGlobalProjectErrors(): readonly Diagnostic[];
getAllProjectErrors(): readonly Diagnostic[];
getLanguageService(ensureSynchronized?: boolean): LanguageService;
private shouldEmitFile;
getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[];
/**
* Returns true if emit was conducted
@ -8610,11 +8610,25 @@ declare namespace ts.server {
private typeAcquisition;
private directoriesWatchedForWildcards;
readonly canonicalConfigFilePath: NormalizedPath;
private projectReferenceCallbacks;
private mapOfDeclarationDirectories;
/** Ref count to the project when opened from external project */
private externalProjectRefCount;
private projectErrors;
private projectReferences;
protected isInitialLoadPending: () => boolean;
/**
* 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
*/
fileExists(file: string): boolean;
/**
* This implementation of directoryExists checks if the directory being requested is
* directory of .d.ts file for the referenced Project.
* If it is it returns true irrespective of whether that directory exists on host
*/
directoryExists(path: string): boolean;
/**
* If the project has reload from disk pending, it reloads (and then updates graph as part of that) instead of just updating the graph
* @returns: true if set of files in the project stays the same and false - otherwise.

View file

@ -2567,6 +2567,7 @@ declare namespace ts {
emitDeclarationOnly?: boolean;
declarationDir?: string;
disableSizeLimit?: boolean;
disableSourceOfProjectReferenceRedirect?: boolean;
downlevelIteration?: boolean;
emitBOM?: boolean;
emitDecoratorMetadata?: boolean;

View file

@ -0,0 +1,5 @@
{
"compilerOptions": {
"disableSourceOfProjectReferenceRedirect": true
}
}