From d0282b75a112a211b7f5d92c62ebf0720bf9c16a Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 13 Jun 2019 14:22:37 -0700 Subject: [PATCH 01/25] Add test to verify when source changes --- .../unittests/tsserver/projectReferences.ts | 75 ++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index 1b2e90bcd4..eb3d83e2b9 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -169,6 +169,8 @@ fn5(); expectedResponse: Response; expectedResponseNoMap?: Response; expectedResponseNoDts?: Response; + requestDependencyChange?: Partial; + expectedResponseDependencyChange: Response; } function gotoDefintinionFromMainTs(fn: number): SessionAction { const textSpan = usageSpan(fn); @@ -200,6 +202,11 @@ fn5(); // To import declaration definitions: [{ file: mainTs.path, ...importSpan(fn) }], textSpan + }, + expectedResponseDependencyChange: { + // Definition on fn + 1 line + definitions: [{ file: dependencyTs.path, ...declarationSpan(fn + 1) }], + textSpan } }; } @@ -227,6 +234,8 @@ fn5(); function renameFromDependencyTs(fn: number): SessionAction { const defSpan = declarationSpan(fn); const { contextStart: _, contextEnd: _1, ...triggerSpan } = defSpan; + const defSpanPlusOne = declarationSpan(fn + 1); + const { contextStart: _2, contextEnd: _3, ...triggerSpanPlusOne } = defSpanPlusOne; return { reqName: "rename", request: { @@ -246,12 +255,30 @@ fn5(); locs: [ { file: dependencyTs.path, locs: [defSpan] } ] + }, + requestDependencyChange: { + command: protocol.CommandTypes.Rename, + arguments: { file: dependencyTs.path, ...triggerSpanPlusOne.start } + }, + expectedResponseDependencyChange: { + info: { + canRename: true, + fileToRename: undefined, + displayName: `fn${fn}`, + fullDisplayName: `"${dependecyLocation}/FnS".fn${fn}`, + kind: ScriptElementKind.functionElement, + kindModifiers: "export", + triggerSpan: triggerSpanPlusOne + }, + locs: [ + { file: dependencyTs.path, locs: [defSpanPlusOne] } + ] } }; } function renameFromDependencyTsWithBothProjectsOpen(fn: number): SessionAction { - const { reqName, request, expectedResponse } = renameFromDependencyTs(fn); + const { reqName, request, expectedResponse, expectedResponseDependencyChange, requestDependencyChange } = renameFromDependencyTs(fn); const { info, locs } = expectedResponse; return { reqName, @@ -271,7 +298,21 @@ fn5(); }, // Only dependency result expectedResponseNoMap: expectedResponse, - expectedResponseNoDts: expectedResponse + expectedResponseNoDts: expectedResponse, + requestDependencyChange, + expectedResponseDependencyChange: { + info: expectedResponseDependencyChange.info, + locs: [ + expectedResponseDependencyChange.locs[0], + { + file: mainTs.path, + locs: [ + importSpan(fn), + usageSpan(fn) + ] + } + ] + } }; } @@ -633,6 +674,36 @@ fn5(); verifyMainScenarioAndScriptInfoCollectionWithNoDts, /*noDts*/ true ); + + it("when defining project source changes", () => { + const { host, session } = openTsFile(); + + // First action + firstAction(session); + + // Make change, without rebuild of solution + if (contains(openInfos, dependencyTs.path)) { + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } +`} + }); + } + else { + host.writeFile(dependencyTs.path, `function fooBar() { } +${dependencyTs.content}`); + } + host.runQueuedTimeoutCallbacks(); + + for (const actionGetter of actionGetters) { + for (let fn = 1; fn <= 5; fn++) { + const { reqName, request, requestDependencyChange, expectedResponseDependencyChange } = actionGetter(fn); + const { response } = session.executeCommandSeq(requestDependencyChange || request); + assert.deepEqual(response, expectedResponseDependencyChange, `Failed on ${reqName}`); + } + } + }); } const usageVerifier: DocumentPositionMapperVerifier = { From 0adab8934aad26fb3a5883b64f1198943700c0f2 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 21 Jun 2019 13:11:39 -0700 Subject: [PATCH 02/25] Use source files instead of .d.ts files from project references --- src/compiler/program.ts | 86 +++++++++++++++---- src/compiler/types.ts | 9 ++ src/server/editorServices.ts | 4 +- src/server/project.ts | 26 ++++++ src/services/services.ts | 6 ++ src/services/types.ts | 4 + .../reference/api/tsserverlibrary.d.ts | 2 + 7 files changed, 119 insertions(+), 18 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 2b62da72d2..27bd1fd3d3 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -813,6 +813,8 @@ namespace ts { let resolvedProjectReferences: ReadonlyArray | undefined; let projectReferenceRedirects: Map | undefined; let mapFromFileToProjectReferenceRedirects: Map | undefined; + let mapFromToProjectReferenceRedirectSource: Map | undefined; + const useSourceOfReference = host.useSourceInsteadOfReferenceRedirect && host.useSourceInsteadOfReferenceRedirect(); const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options); const structuralIsReused = tryReuseStructureFromOldProgram(); @@ -824,17 +826,29 @@ namespace ts { if (!resolvedProjectReferences) { resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile); } + if (host.setGetSourceOfProjectReferenceRedirect) { + host.setGetSourceOfProjectReferenceRedirect(getSourceOfProjectReferenceRedirect); + } 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 (useSourceOfReference) { + 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); + } } } } @@ -1212,6 +1226,9 @@ namespace ts { } if (projectReferences) { resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile); + if (host.setGetSourceOfProjectReferenceRedirect) { + host.setGetSourceOfProjectReferenceRedirect(getSourceOfProjectReferenceRedirect); + } } // check if program source files has changed in the way that can affect structure of the program @@ -2220,6 +2237,14 @@ namespace ts { // Get source file from normalized fileName function findSourceFile(fileName: string, path: Path, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, refFile: SourceFile, refPos: number, refEnd: number, packageId: PackageId | undefined): SourceFile | undefined { + if (useSourceOfReference) { + const source = getSourceOfProjectReferenceRedirect(fileName); + if (source) { + return isString(source) ? + findSourceFile(source, toPath(source), isDefaultLib, ignoreNoDefaultLib, refFile, refPos, refEnd, packageId) : + undefined; + } + } const originalFileName = fileName; if (filesByName.has(path)) { const file = filesByName.get(path); @@ -2267,7 +2292,7 @@ namespace ts { } let redirectedPath: Path | undefined; - if (refFile) { + if (refFile && !useSourceOfReference) { const redirectProject = getProjectReferenceRedirectProject(fileName); if (redirectProject) { if (redirectProject.commandLine.options.outFile || redirectProject.commandLine.options.out) { @@ -2286,15 +2311,20 @@ namespace ts { } // We haven't looked for this file, do so now and cache result - const file = host.getSourceFile(fileName, options.target!, hostErrorMessage => { // TODO: GH#18217 - if (refFile !== undefined && refPos !== undefined && refEnd !== undefined) { - fileProcessingDiagnostics.add(createFileDiagnostic(refFile, refPos, refEnd - refPos, - Diagnostics.Cannot_read_file_0_Colon_1, fileName, hostErrorMessage)); - } - else { - fileProcessingDiagnostics.add(createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, hostErrorMessage)); - } - }, shouldCreateNewSourceFile); + const file = host.getSourceFile( + fileName, + options.target!, + hostErrorMessage => { // TODO: GH#18217 + if (refFile !== undefined && refPos !== undefined && refEnd !== undefined) { + fileProcessingDiagnostics.add(createFileDiagnostic(refFile, refPos, refEnd - refPos, + Diagnostics.Cannot_read_file_0_Colon_1, fileName, hostErrorMessage)); + } + else { + fileProcessingDiagnostics.add(createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, hostErrorMessage)); + } + }, + shouldCreateNewSourceFile + ); if (packageId) { const packageIdKey = packageIdToString(packageId); @@ -2424,6 +2454,30 @@ 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 => { + const outputDts = getOutputDeclarationFileName(fileName, resolvedRef.commandLine, host.useCaseSensitiveFileNames()); + mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), fileName); + }); + } + } + }); + } + return mapFromToProjectReferenceRedirectSource.get(toPath(file)); + } + function forEachProjectReference( projectReferences: ReadonlyArray | undefined, resolvedProjectReferences: ReadonlyArray | undefined, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ec46da039d..fc24088e0f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5166,11 +5166,20 @@ namespace ts { /* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean; createHash?(data: string): string; getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined; + /* @internal */ setGetSourceOfProjectReferenceRedirect?(getSource: GetSourceOfProjectReferenceRedirect): void; + /* @internal */ useSourceInsteadOfReferenceRedirect?(): 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 type GetSourceOfProjectReferenceRedirect = (fileName: string) => SourceOfProjectReferenceRedirect | undefined; + /* @internal */ export const enum TransformFlags { None = 0, diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 4e5435feab..f7fb537f54 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1798,7 +1798,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)) { @@ -1831,7 +1831,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); diff --git a/src/server/project.ts b/src/server/project.ts index d1605e22d1..3e69a78551 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -381,6 +381,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); @@ -1369,6 +1374,7 @@ namespace ts.server { configFileWatcher: FileWatcher | undefined; private directoriesWatchedForWildcards: Map | undefined; readonly canonicalConfigFilePath: NormalizedPath; + private getSourceOfProjectReferenceRedirect: GetSourceOfProjectReferenceRedirect | undefined; /* @internal */ pendingReload: ConfigFileProgramReloadLevel | undefined; @@ -1414,6 +1420,25 @@ namespace ts.server { this.canonicalConfigFilePath = asNormalizedPath(projectService.toCanonicalFileName(configFileName)); } + /* @internal */ + setGetSourceOfProjectReferenceRedirect(getSource: GetSourceOfProjectReferenceRedirect) { + this.getSourceOfProjectReferenceRedirect = getSource; + } + + /* @internal */ + useSourceInsteadOfReferenceRedirect() { + return true; + } + + fileExists(file: string): boolean { + // Project references go to source file instead of .d.ts file + if (this.getSourceOfProjectReferenceRedirect) { + const source = this.getSourceOfProjectReferenceRedirect(file); + if (source) return isString(source) ? super.fileExists(source) : true; + } + return super.fileExists(file); + } + /** * 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. @@ -1436,6 +1461,7 @@ namespace ts.server { default: result = super.updateGraph(); } + this.getSourceOfProjectReferenceRedirect = undefined; this.projectService.sendProjectLoadingFinishEvent(this); this.projectService.sendProjectTelemetry(this); return result; diff --git a/src/services/services.ts b/src/services/services.ts index fab6f88b77..a8b05402e7 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1245,6 +1245,12 @@ namespace ts { return host.resolveTypeReferenceDirectives!(typeReferenceDirectiveNames, containingFile, redirectedReference); }; } + if (host.setGetSourceOfProjectReferenceRedirect) { + compilerHost.setGetSourceOfProjectReferenceRedirect = getSource => host.setGetSourceOfProjectReferenceRedirect!(getSource); + } + if (host.useSourceInsteadOfReferenceRedirect) { + compilerHost.useSourceInsteadOfReferenceRedirect = () => host.useSourceInsteadOfReferenceRedirect!(); + } const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings); const options: CreateProgramOptions = { diff --git a/src/services/types.ts b/src/services/types.ts index b97125734f..3c9509ca5e 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -236,6 +236,10 @@ namespace ts { getDocumentPositionMapper?(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined; /* @internal */ getSourceFileLike?(fileName: string): SourceFileLike | undefined; + /* @internal */ + setGetSourceOfProjectReferenceRedirect?(getSource: GetSourceOfProjectReferenceRedirect): void; + /* @internal */ + useSourceInsteadOfReferenceRedirect?(): boolean; } /* @internal */ diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 82ab70b6e6..53459d993c 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -8546,11 +8546,13 @@ declare namespace ts.server { private typeAcquisition; private directoriesWatchedForWildcards; readonly canonicalConfigFilePath: NormalizedPath; + private getSourceOfProjectReferenceRedirect; /** Ref count to the project when opened from external project */ private externalProjectRefCount; private projectErrors; private projectReferences; protected isInitialLoadPending: () => boolean; + fileExists(file: 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. From c97be16fa192e4c6ce04e5867b0a9e042cfdb392 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 21 Jun 2019 13:54:06 -0700 Subject: [PATCH 03/25] Log the config of the project --- src/server/editorServices.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index f7fb537f54..1b2666d4cb 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1757,6 +1757,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; From 746b01e5772d5eda7f71a72265c5fee3a3a6ba8e Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 21 Jun 2019 14:16:40 -0700 Subject: [PATCH 04/25] Check only for .d.ts files --- src/compiler/program.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 27bd1fd3d3..6fee35d244 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -2468,8 +2468,10 @@ namespace ts { } else { forEach(resolvedRef.commandLine.fileNames, fileName => { - const outputDts = getOutputDeclarationFileName(fileName, resolvedRef.commandLine, host.useCaseSensitiveFileNames()); - mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), fileName); + if (!fileExtensionIs(fileName, Extension.Dts) && hasTSFileExtension(fileName)) { + const outputDts = getOutputDeclarationFileName(fileName, resolvedRef.commandLine, host.useCaseSensitiveFileNames()); + mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), fileName); + } }); } } From ecf875112b7a7faea715a60ac1ee4da8c201bb38 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 21 Jun 2019 14:22:11 -0700 Subject: [PATCH 05/25] Check for language serivice enabled when including source files --- src/server/project.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/project.ts b/src/server/project.ts index 3e69a78551..3b721c14b5 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1427,12 +1427,12 @@ namespace ts.server { /* @internal */ useSourceInsteadOfReferenceRedirect() { - return true; + return !!this.languageServiceEnabled; } fileExists(file: string): boolean { // Project references go to source file instead of .d.ts file - if (this.getSourceOfProjectReferenceRedirect) { + if (this.languageServiceEnabled && this.getSourceOfProjectReferenceRedirect) { const source = this.getSourceOfProjectReferenceRedirect(file); if (source) return isString(source) ? super.fileExists(source) : true; } From 181028821ba5600324b13b645f93b098747f6d5a Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 21 Jun 2019 14:54:38 -0700 Subject: [PATCH 06/25] Fix tests --- src/testRunner/unittests/tsserver/projectReferences.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index eb3d83e2b9..d5a6725282 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -85,8 +85,8 @@ namespace ts.projectSystem { }); const { file: _, ...renameTextOfMyConstInLib } = locationOfMyConstInLib; assert.deepEqual(response.locs, [ - { file: myConstFile, locs: [{ start: myConstStart, end: myConstEnd }] }, - { file: locationOfMyConstInLib.file, locs: [renameTextOfMyConstInLib] } + { file: locationOfMyConstInLib.file, locs: [renameTextOfMyConstInLib] }, + { file: myConstFile, locs: [{ start: myConstStart, end: myConstEnd }] } ]); }); }); From f4728682b7f6a8275b034fb9be5a2f64b84e0aea Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 25 Jun 2019 12:15:04 -0700 Subject: [PATCH 07/25] Watch generated file if it doesnt exist when trying to translate it to to source generated position --- src/server/editorServices.ts | 8 +- src/server/project.ts | 100 +++++++++ src/server/utilities.ts | 1 + .../unittests/tsserver/projectReferences.ts | 200 ++++++++++-------- .../reference/api/tsserverlibrary.d.ts | 4 + 5 files changed, 229 insertions(+), 84 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 1b2666d4cb..8c7462d857 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -2240,7 +2240,13 @@ namespace ts.server { getDocumentPositionMapper(project: Project, generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined { // Since declaration info and map file watches arent updating project's directory structure host (which can cache file structure) use host const declarationInfo = this.getOrCreateScriptInfoNotOpenedByClient(generatedFileName, project.currentDirectory, this.host); - if (!declarationInfo) return undefined; + if (!declarationInfo) { + if (sourceFileName) { + // Project contains source file and it generates the generated file name + project.addGeneratedFileWatch(generatedFileName, sourceFileName); + } + return undefined; + } // Try to get from cache declarationInfo.getSnapshot(); // Ensure synchronized diff --git a/src/server/project.ts b/src/server/project.ts index 3b721c14b5..8f8e6e5da8 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -109,12 +109,22 @@ namespace ts.server { return value instanceof ScriptInfo; } + interface GeneratedFileWatcher { + generatedFilePath: Path; + watcher: FileWatcher; + } + type GeneratedFileWatcherMap = GeneratedFileWatcher | Map; + function isGeneratedFileWatcher(watch: GeneratedFileWatcherMap): watch is GeneratedFileWatcher { + return (watch as GeneratedFileWatcher).generatedFilePath !== undefined; + } + export abstract class Project implements LanguageServiceHost, ModuleResolutionHost { private rootFiles: ScriptInfo[] = []; private rootFilesMap: Map = createMap(); private program: Program | undefined; private externalFiles: SortedReadonlyArray | undefined; private missingFilesMap: Map | undefined; + private generatedFilesMap: GeneratedFileWatcherMap | undefined; private plugins: PluginModuleWithName[] = []; /*@internal*/ @@ -573,6 +583,7 @@ namespace ts.server { this.lastFileExceededProgramSize = lastFileExceededProgramSize; this.builderState = undefined; this.resolutionCache.closeTypeRootsWatch(); + this.clearGeneratedFileWatch(); this.projectService.onUpdateLanguageServiceStateForProject(this, /*languageServiceEnabled*/ false); } @@ -654,6 +665,7 @@ namespace ts.server { clearMap(this.missingFilesMap, closeFileWatcher); this.missingFilesMap = undefined!; } + this.clearGeneratedFileWatch(); // signal language service to release source files acquired from document registry this.languageService.dispose(); @@ -947,6 +959,39 @@ namespace ts.server { missingFilePath => this.addMissingFileWatcher(missingFilePath) ); + if (this.generatedFilesMap) { + const outPath = this.compilerOptions.outFile && this.compilerOptions.out; + if (isGeneratedFileWatcher(this.generatedFilesMap)) { + // --out + if (!outPath || !this.isValidGeneratedFileWatcher( + removeFileExtension(outPath) + Extension.Dts, + this.generatedFilesMap, + )) { + this.clearGeneratedFileWatch(); + } + } + else { + // MultiFile + if (outPath) { + this.clearGeneratedFileWatch(); + } + else { + this.generatedFilesMap.forEach((watcher, source) => { + const sourceFile = this.program!.getSourceFileByPath(source as Path); + if (!sourceFile || + sourceFile.resolvedPath !== source || + !this.isValidGeneratedFileWatcher( + getDeclarationEmitOutputFilePathWorker(sourceFile.fileName, this.compilerOptions, this.currentDirectory, this.program!.getCommonSourceDirectory(), this.getCanonicalFileName), + watcher + )) { + closeFileWatcherOf(watcher); + (this.generatedFilesMap as Map).delete(source); + } + }); + } + } + } + // Watch the type locations that would be added to program as part of automatic type resolutions if (this.languageServiceEnabled) { this.resolutionCache.updateTypeRootsWatch(); @@ -1011,6 +1056,61 @@ namespace ts.server { return !!this.missingFilesMap && this.missingFilesMap.has(path); } + /* @internal */ + addGeneratedFileWatch(generatedFile: string, sourceFile: string) { + if (this.compilerOptions.outFile || this.compilerOptions.out) { + // Single watcher + if (!this.generatedFilesMap) { + this.generatedFilesMap = this.createGeneratedFileWatcher(generatedFile); + } + } + else { + // Map + const path = this.toPath(sourceFile); + if (this.generatedFilesMap) { + if (isGeneratedFileWatcher(this.generatedFilesMap)) { + Debug.fail(`${this.projectName} Expected not to have --out watcher for generated file with options: ${JSON.stringify(this.compilerOptions)}`); + return; + } + if (this.generatedFilesMap.has(path)) return; + } + else { + this.generatedFilesMap = createMap(); + } + this.generatedFilesMap.set(path, this.createGeneratedFileWatcher(generatedFile)); + } + } + + private createGeneratedFileWatcher(generatedFile: string): GeneratedFileWatcher { + return { + generatedFilePath: this.toPath(generatedFile), + watcher: this.projectService.watchFactory.watchFile( + this.projectService.host, + generatedFile, + () => this.projectService.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(this), + PollingInterval.High, + WatchType.MissingGeneratedFile, + this + ) + }; + } + + private isValidGeneratedFileWatcher(generateFile: string, watcher: GeneratedFileWatcher) { + return this.toPath(generateFile) === watcher.generatedFilePath; + } + + private clearGeneratedFileWatch() { + if (this.generatedFilesMap) { + if (isGeneratedFileWatcher(this.generatedFilesMap)) { + closeFileWatcherOf(this.generatedFilesMap); + } + else { + clearMap(this.generatedFilesMap, closeFileWatcherOf); + } + this.generatedFilesMap = undefined; + } + } + getScriptInfoForNormalizedPath(fileName: NormalizedPath): ScriptInfo | undefined { const scriptInfo = this.projectService.getScriptInfoForPath(this.toPath(fileName)); if (scriptInfo && !scriptInfo.isAttached(this)) { diff --git a/src/server/utilities.ts b/src/server/utilities.ts index 91880ccfdb..fd38c6a432 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -227,5 +227,6 @@ namespace ts { NodeModulesForClosedScriptInfo = "node_modules for closed script infos in them", MissingSourceMapFile = "Missing source map file", NoopConfigFileForInferredRoot = "Noop Config file for the inferred project root", + MissingGeneratedFile = "Missing generated file" } } diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index d5a6725282..9c02b670bb 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -94,6 +94,7 @@ namespace ts.projectSystem { describe("with main and depedency project", () => { const projectLocation = "/user/username/projects/myproject"; const dependecyLocation = `${projectLocation}/dependency`; + const dependecyDeclsLocation = `${projectLocation}/decls`; const mainLocation = `${projectLocation}/main`; const dependencyTs: File = { path: `${dependecyLocation}/FnS.ts`, @@ -106,7 +107,7 @@ export function fn5() { } }; const dependencyConfig: File = { path: `${dependecyLocation}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { composite: true, declarationMap: true } }) + content: JSON.stringify({ compilerOptions: { composite: true, declarationMap: true, declarationDir: "../decls" } }) }; const mainTs: File = { @@ -117,7 +118,7 @@ export function fn5() { } fn3, fn4, fn5 -} from '../dependency/fns' +} from '../decls/fns' fn1(); fn2(); @@ -142,9 +143,9 @@ fn5(); path: `${projectLocation}/random/tsconfig.json`, content: "{}" }; - const dtsLocation = `${dependecyLocation}/FnS.d.ts`; + const dtsLocation = `${dependecyDeclsLocation}/FnS.d.ts`; const dtsPath = dtsLocation.toLowerCase() as Path; - const dtsMapLocation = `${dtsLocation}.map`; + const dtsMapLocation = `${dependecyDeclsLocation}/FnS.d.ts.map`; const dtsMapPath = dtsMapLocation.toLowerCase() as Path; const files = [dependencyTs, dependencyConfig, mainTs, mainConfig, libFile, randomFile, randomConfig]; @@ -224,7 +225,7 @@ fn5(); start: { line: fn + 1, offset: 5 }, end: { line: fn + 1, offset: 8 }, contextStart: { line: 1, offset: 1 }, - contextEnd: { line: 7, offset: 27 } + contextEnd: { line: 7, offset: 22 } }; } function usageSpan(fn: number): protocol.TextSpan { @@ -328,19 +329,25 @@ fn5(); function verifyDocumentPositionMapperUpdates( mainScenario: string, verifier: ReadonlyArray, - closedInfos: ReadonlyArray) { + closedInfos: ReadonlyArray, + withRefs: boolean) { const openFiles = verifier.map(v => v.openFile); const expectedProjectActualFiles = verifier.map(v => v.expectedProjectActualFiles); - const actionGetters = verifier.map(v => v.actionGetter); const openFileLastLines = verifier.map(v => v.openFileLastLine); const configFiles = openFiles.map(openFile => `${getDirectoryPath(openFile.path)}/tsconfig.json`); const openInfos = openFiles.map(f => f.path); // When usage and dependency are used, dependency config is part of closedInfo so ignore - const otherWatchedFiles = verifier.length > 1 ? [configFiles[0]] : configFiles; + const otherWatchedFiles = withRefs && verifier.length > 1 ? [configFiles[0]] : configFiles; function openTsFile(onHostCreate?: (host: TestServerHost) => void) { const host = createHost(files, [mainConfig.path]); + if (!withRefs) { + // Erase project reference + host.writeFile(mainConfig.path, JSON.stringify({ + compilerOptions: { composite: true, declarationMap: true } + })); + } if (onHostCreate) { onHostCreate(host); } @@ -377,7 +384,7 @@ fn5(); ); } - function verifyInfosWhenNoDtsFile(session: TestSession, host: TestServerHost, dependencyTsAndMapOk?: true) { + function verifyInfosWhenNoDtsFile(session: TestSession, host: TestServerHost, watchDts: boolean, dependencyTsAndMapOk?: true) { const dtsMapClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsMapPath ? f : undefined); const dtsClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsPath ? f : undefined); verifyInfosWithRandom( @@ -385,8 +392,7 @@ fn5(); host, openInfos, closedInfos.filter(f => (dependencyTsAndMapOk || f !== dtsMapClosedInfo) && f !== dtsClosedInfo && (dependencyTsAndMapOk || f !== dependencyTs.path)), - // When project actual file contains dts, it needs to be watched - dtsClosedInfo && expectedProjectActualFiles.some(expectedProjectActualFiles => expectedProjectActualFiles.some(f => f.toLowerCase() === dtsPath)) ? + dtsClosedInfo && watchDts ? otherWatchedFiles.concat(dtsClosedInfo) : otherWatchedFiles ); @@ -402,22 +408,22 @@ fn5(); } } - function action(actionGetter: SessionActionGetter, fn: number, session: TestSession) { - const { reqName, request, expectedResponse, expectedResponseNoMap, expectedResponseNoDts } = actionGetter(fn); + function action(verifier: DocumentPositionMapperVerifier, fn: number, session: TestSession) { + const { reqName, request, expectedResponse, expectedResponseNoMap, expectedResponseNoDts } = verifier.actionGetter(fn); const { response } = session.executeCommandSeq(request); - return { reqName, response, expectedResponse, expectedResponseNoMap, expectedResponseNoDts }; + return { reqName, response, expectedResponse, expectedResponseNoMap, expectedResponseNoDts, verifier }; } function firstAction(session: TestSession) { - actionGetters.forEach(actionGetter => action(actionGetter, 1, session)); + verifier.forEach(v => action(v, 1, session)); } function verifyAllFnActionWorker(session: TestSession, verifyAction: (result: ReturnType, dtsInfo: server.ScriptInfo | undefined, isFirst: boolean) => void, dtsAbsent?: true) { // action let isFirst = true; - for (const actionGetter of actionGetters) { + for (const v of verifier) { for (let fn = 1; fn <= 5; fn++) { - const result = action(actionGetter, fn, session); + const result = action(v, fn, session); const dtsInfo = session.getProjectService().filenameToScriptInfo.get(dtsPath); if (dtsAbsent) { assert.isUndefined(dtsInfo); @@ -490,9 +496,17 @@ fn5(); dependencyTsAndMapOk?: true ) { // action - verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse, expectedResponseNoDts }) => { + verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse, expectedResponseNoDts, verifier }) => { assert.deepEqual(response, expectedResponseNoDts || expectedResponse, `Failed on ${reqName}`); - verifyInfosWhenNoDtsFile(session, host, dependencyTsAndMapOk); + verifyInfosWhenNoDtsFile( + session, + host, + // Even when project actual file contains dts, its not watched because the dts is in another folder and module resolution just fails + // instead of succeeding to source file and then mapping using project reference (When using usage location) + // But watched if sourcemapper is in source project since we need to keep track of dts to update the source mapper for any potential usages + verifier.expectedProjectActualFiles.every(f => f.toLowerCase() !== dtsPath), + dependencyTsAndMapOk, + ); }, /*dtsAbsent*/ true); } @@ -576,7 +590,11 @@ fn5(); // Collecting at this point retains dependency.d.ts and map watcher closeFilesForSession([randomFile], session); openFilesForSession([randomFile], session); - verifyInfosWhenNoDtsFile(session, host); + verifyInfosWhenNoDtsFile( + session, + host, + !!forEach(verifier, v => v.expectedProjectActualFiles.every(f => f.toLowerCase() !== dtsPath)) + ); // Closing open file, removes dependencies too closeFilesForSession([...openFiles, randomFile], session); @@ -657,7 +675,7 @@ fn5(); "when dependency file's map changes", host => host.writeFile( dtsMapLocation, - `{"version":3,"file":"FnS.d.ts","sourceRoot":"","sources":["FnS.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,eAAO,MAAM,CAAC,KAAK,CAAC"}` + `{"version":3,"file":"FnS.d.ts","sourceRoot":"","sources":["../dependency/FnS.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,eAAO,MAAM,CAAC,KAAK,CAAC"}` ), /*afterActionDocumentPositionMapperNotEquals*/ true ); @@ -675,75 +693,91 @@ fn5(); /*noDts*/ true ); - it("when defining project source changes", () => { - const { host, session } = openTsFile(); + if (withRefs) { + it("when defining project source changes", () => { + const { host, session } = openTsFile(); - // First action - firstAction(session); + // First action + firstAction(session); - // Make change, without rebuild of solution - if (contains(openInfos, dependencyTs.path)) { - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } + // Make change, without rebuild of solution + if (contains(openInfos, dependencyTs.path)) { + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } `} - }); - } - else { - host.writeFile(dependencyTs.path, `function fooBar() { } -${dependencyTs.content}`); - } - host.runQueuedTimeoutCallbacks(); - - for (const actionGetter of actionGetters) { - for (let fn = 1; fn <= 5; fn++) { - const { reqName, request, requestDependencyChange, expectedResponseDependencyChange } = actionGetter(fn); - const { response } = session.executeCommandSeq(requestDependencyChange || request); - assert.deepEqual(response, expectedResponseDependencyChange, `Failed on ${reqName}`); + }); } - } + else { + host.writeFile(dependencyTs.path, `function fooBar() { } +${dependencyTs.content}`); + } + host.runQueuedTimeoutCallbacks(); + + for (const v of verifier) { + for (let fn = 1; fn <= 5; fn++) { + const { reqName, request, requestDependencyChange, expectedResponseDependencyChange } = v.actionGetter(fn); + const { response } = session.executeCommandSeq(requestDependencyChange || request); + assert.deepEqual(response, expectedResponseDependencyChange, `Failed on ${reqName}`); + } + } + }); + } + } + + function verifyScenarios(withRefs: boolean) { + describe(withRefs ? "when main tsconfig has project reference" : "when main tsconfig doesnt have project reference", () => { + const usageVerifier: DocumentPositionMapperVerifier = { + openFile: mainTs, + expectedProjectActualFiles: [mainTs.path, libFile.path, mainConfig.path, dtsPath], + actionGetter: gotoDefintinionFromMainTs, + openFileLastLine: 14 + }; + describe("from project that uses dependency", () => { + const closedInfos = withRefs ? + [dependencyTs.path, dependencyConfig.path, libFile.path, dtsPath, dtsMapLocation] : + [dependencyTs.path, libFile.path, dtsPath, dtsMapLocation]; + verifyDocumentPositionMapperUpdates( + "can go to definition correctly", + [usageVerifier], + closedInfos, + withRefs + ); + }); + + const definingVerifier: DocumentPositionMapperVerifier = { + openFile: dependencyTs, + expectedProjectActualFiles: [dependencyTs.path, libFile.path, dependencyConfig.path], + actionGetter: renameFromDependencyTs, + openFileLastLine: 6, + }; + describe("from defining project", () => { + const closedInfos = [libFile.path, dtsLocation, dtsMapLocation]; + verifyDocumentPositionMapperUpdates( + "rename locations from dependency", + [definingVerifier], + closedInfos, + withRefs + ); + }); + + describe("when opening depedency and usage project", () => { + const closedInfos = withRefs ? + [libFile.path, dtsPath, dtsMapLocation, dependencyConfig.path] : + [libFile.path, dtsPath, dtsMapLocation]; + verifyDocumentPositionMapperUpdates( + "goto Definition in usage and rename locations from defining project", + [usageVerifier, { ...definingVerifier, actionGetter: renameFromDependencyTsWithBothProjectsOpen }], + closedInfos, + withRefs + ); + }); }); } - const usageVerifier: DocumentPositionMapperVerifier = { - openFile: mainTs, - expectedProjectActualFiles: [mainTs.path, libFile.path, mainConfig.path, dtsPath], - actionGetter: gotoDefintinionFromMainTs, - openFileLastLine: 14 - }; - describe("from project that uses dependency", () => { - const closedInfos = [dependencyTs.path, dependencyConfig.path, libFile.path, dtsPath, dtsMapLocation]; - verifyDocumentPositionMapperUpdates( - "can go to definition correctly", - [usageVerifier], - closedInfos - ); - }); - - const definingVerifier: DocumentPositionMapperVerifier = { - openFile: dependencyTs, - expectedProjectActualFiles: [dependencyTs.path, libFile.path, dependencyConfig.path], - actionGetter: renameFromDependencyTs, - openFileLastLine: 6 - }; - describe("from defining project", () => { - const closedInfos = [libFile.path, dtsLocation, dtsMapLocation]; - verifyDocumentPositionMapperUpdates( - "rename locations from dependency", - [definingVerifier], - closedInfos - ); - }); - - describe("when opening depedency and usage project", () => { - const closedInfos = [libFile.path, dtsPath, dtsMapLocation, dependencyConfig.path]; - verifyDocumentPositionMapperUpdates( - "goto Definition in usage and rename locations from defining project", - [usageVerifier, { ...definingVerifier, actionGetter: renameFromDependencyTsWithBothProjectsOpen }], - closedInfos - ); - }); + verifyScenarios(/*withRefs*/ false); + verifyScenarios(/*withRefs*/ true); }); }); } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 53459d993c..286516f37a 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -8397,6 +8397,7 @@ declare namespace ts.server { private program; private externalFiles; private missingFilesMap; + private generatedFilesMap; private plugins; private lastFileExceededProgramSize; protected languageService: LanguageService; @@ -8509,6 +8510,9 @@ declare namespace ts.server { private detachScriptInfoFromProject; private addMissingFileWatcher; private isWatchedMissingFile; + private createGeneratedFileWatcher; + private isValidGeneratedFileWatcher; + private clearGeneratedFileWatch; getScriptInfoForNormalizedPath(fileName: NormalizedPath): ScriptInfo | undefined; getScriptInfo(uncheckedFileName: string): ScriptInfo | undefined; filesToString(writeProjectFileNames: boolean): string; From 012ecdacde36330ecd1eab532dbe1d83624d87be Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 26 Jun 2019 13:41:46 -0700 Subject: [PATCH 08/25] Add sourceOf project reference redirect to filesByName list for redirect path so that module symbol is correctly resolved --- src/compiler/program.ts | 11 +- src/services/services.ts | 9 +- src/services/sourcemaps.ts | 6 + .../unittests/tsserver/projectReferences.ts | 158 +++++++++--------- 4 files changed, 103 insertions(+), 81 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 6fee35d244..9a4e26a290 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1411,6 +1411,13 @@ namespace ts { for (const newSourceFile of newSourceFiles) { const filePath = newSourceFile.path; addFileToFilesByName(newSourceFile, filePath, newSourceFile.resolvedPath); + if (useSourceOfReference) { + 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(filePath)!)) { sourceFilesFoundSearchingNodeModules.set(filePath, true); @@ -2240,9 +2247,11 @@ namespace ts { if (useSourceOfReference) { const source = getSourceOfProjectReferenceRedirect(fileName); if (source) { - return isString(source) ? + const file = isString(source) ? findSourceFile(source, toPath(source), isDefaultLib, ignoreNoDefaultLib, refFile, refPos, refEnd, packageId) : undefined; + if (file) addFileToFilesByName(file, path, /*redirectedPath*/ undefined); + return file; } } const originalFileName = fileName; diff --git a/src/services/services.ts b/src/services/services.ts index a8b05402e7..aa6ea11922 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1148,10 +1148,11 @@ 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), + useSourceInsteadOfReferenceRedirect: maybeBind(host, host.useSourceInsteadOfReferenceRedirect), + getSourceFileLike: maybeBind(host, host.getSourceFileLike), log }); diff --git a/src/services/sourcemaps.ts b/src/services/sourcemaps.ts index d07c21a9f4..de590d4e5b 100644 --- a/src/services/sourcemaps.ts +++ b/src/services/sourcemaps.ts @@ -17,6 +17,7 @@ namespace ts { readFile?(path: string, encoding?: string): string | undefined; getSourceFileLike?(fileName: string): SourceFileLike | undefined; getDocumentPositionMapper?(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined; + /* @internal */ useSourceInsteadOfReferenceRedirect?(): boolean; log(s: string): void; } @@ -70,6 +71,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 (host.useSourceInsteadOfReferenceRedirect && + host.useSourceInsteadOfReferenceRedirect() && + program.getResolvedProjectReferenceToRedirect(sourceFile.fileName)) return undefined; + const options = program.getCompilerOptions(); const outPath = options.outFile || options.out; diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index 9c02b670bb..5320cfe13b 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -373,7 +373,7 @@ fn5(); verifyInfosWithRandom(session, host, openInfos, closedInfos, otherWatchedFiles); } - function verifyInfosWhenNoMapFile(session: TestSession, host: TestServerHost, dependencyTsOK?: true) { + function verifyInfosWhenNoMapFile(session: TestSession, host: TestServerHost, dependencyTsOK?: boolean) { const dtsMapClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsMapPath ? f : undefined); verifyInfosWithRandom( session, @@ -384,46 +384,48 @@ fn5(); ); } - function verifyInfosWhenNoDtsFile(session: TestSession, host: TestServerHost, watchDts: boolean, dependencyTsAndMapOk?: true) { + function verifyInfosWhenNoDtsFile(session: TestSession, host: TestServerHost, watchDts: boolean, dependencyTsOk?: boolean, depedencyMapOk?: boolean) { const dtsMapClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsMapPath ? f : undefined); const dtsClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsPath ? f : undefined); verifyInfosWithRandom( session, host, openInfos, - closedInfos.filter(f => (dependencyTsAndMapOk || f !== dtsMapClosedInfo) && f !== dtsClosedInfo && (dependencyTsAndMapOk || f !== dependencyTs.path)), + closedInfos.filter(f => (depedencyMapOk || f !== dtsMapClosedInfo) && f !== dtsClosedInfo && (dependencyTsOk || f !== dependencyTs.path)), dtsClosedInfo && watchDts ? otherWatchedFiles.concat(dtsClosedInfo) : otherWatchedFiles ); } - function verifyDocumentPositionMapper(session: TestSession, dependencyMap: server.ScriptInfo, documentPositionMapper: server.ScriptInfo["documentPositionMapper"], notEqual?: true) { + function verifyDocumentPositionMapper(session: TestSession, dependencyMap: server.ScriptInfo | undefined, documentPositionMapper: server.ScriptInfo["documentPositionMapper"], notEqual?: true) { assert.strictEqual(session.getProjectService().filenameToScriptInfo.get(dtsMapPath), dependencyMap); - if (notEqual) { - assert.notStrictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); - } - else { - assert.strictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); + if (dependencyMap) { + if (notEqual) { + assert.notStrictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); + } + else { + assert.strictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); + } } } - function action(verifier: DocumentPositionMapperVerifier, fn: number, session: TestSession) { - const { reqName, request, expectedResponse, expectedResponseNoMap, expectedResponseNoDts } = verifier.actionGetter(fn); - const { response } = session.executeCommandSeq(request); - return { reqName, response, expectedResponse, expectedResponseNoMap, expectedResponseNoDts, verifier }; + function action(verifier: DocumentPositionMapperVerifier, fn: number, session: TestSession, useDependencyChange?: boolean) { + const { reqName, request, expectedResponse, expectedResponseNoMap, expectedResponseNoDts, requestDependencyChange, expectedResponseDependencyChange } = verifier.actionGetter(fn); + const { response } = session.executeCommandSeq(useDependencyChange ? requestDependencyChange || request : request); + return { reqName, response, expectedResponse, expectedResponseNoMap, expectedResponseNoDts, expectedResponseDependencyChange, verifier }; } function firstAction(session: TestSession) { verifier.forEach(v => action(v, 1, session)); } - function verifyAllFnActionWorker(session: TestSession, verifyAction: (result: ReturnType, dtsInfo: server.ScriptInfo | undefined, isFirst: boolean) => void, dtsAbsent?: true) { + function verifyAllFnActionWorker(session: TestSession, verifyAction: (result: ReturnType, dtsInfo: server.ScriptInfo | undefined, isFirst: boolean) => void, dtsAbsent?: boolean, useDependencyChange?: boolean) { // action let isFirst = true; for (const v of verifier) { for (let fn = 1; fn <= 5; fn++) { - const result = action(v, fn, session); + const result = action(v, fn, session, useDependencyChange); const dtsInfo = session.getProjectService().filenameToScriptInfo.get(dtsPath); if (dtsAbsent) { assert.isUndefined(dtsInfo); @@ -437,33 +439,38 @@ fn5(); } } + function dtsAbsent() { + return withRefs && !contains(closedInfos, dtsPath, (a, b) => a.toLowerCase() === b.toLowerCase()); + } + function verifyAllFnAction( session: TestSession, host: TestServerHost, firstDocumentPositionMapperNotEquals?: true, dependencyMap?: server.ScriptInfo, - documentPositionMapper?: server.ScriptInfo["documentPositionMapper"] + documentPositionMapper?: server.ScriptInfo["documentPositionMapper"], + useDependencyChange?: boolean ) { // action - verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse }, dtsInfo, isFirst) => { - assert.deepEqual(response, expectedResponse, `Failed on ${reqName}`); + verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse, expectedResponseDependencyChange }, dtsInfo, isFirst) => { + assert.deepEqual(response, useDependencyChange ? expectedResponseDependencyChange || expectedResponse : expectedResponse, `Failed on ${reqName}`); verifyInfos(session, host); - assert.equal(dtsInfo!.sourceMapFilePath, dtsMapPath); + if (dtsInfo) assert.equal(dtsInfo.sourceMapFilePath, dtsMapPath); if (isFirst) { if (dependencyMap) { verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, firstDocumentPositionMapperNotEquals); documentPositionMapper = dependencyMap.documentPositionMapper; } else { - dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath)!; - documentPositionMapper = dependencyMap.documentPositionMapper; + dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath); + documentPositionMapper = dependencyMap && dependencyMap.documentPositionMapper; } } else { - verifyDocumentPositionMapper(session, dependencyMap!, documentPositionMapper); + verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper); } - }); - return { dependencyMap: dependencyMap!, documentPositionMapper }; + }, dtsAbsent(), useDependencyChange); + return { dependencyMap, documentPositionMapper }; } function verifyAllFnActionWithNoMap( @@ -474,19 +481,21 @@ fn5(); let sourceMapFilePath: server.ScriptInfo["sourceMapFilePath"]; // action verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse, expectedResponseNoMap }, dtsInfo, isFirst) => { - assert.deepEqual(response, expectedResponseNoMap || expectedResponse, `Failed on ${reqName}`); + assert.deepEqual(response, withRefs ? expectedResponse : expectedResponseNoMap || expectedResponse, `Failed on ${reqName}`); verifyInfosWhenNoMapFile(session, host, dependencyTsOK); assert.isUndefined(session.getProjectService().filenameToScriptInfo.get(dtsMapPath)); - if (isFirst) { - assert.isNotString(dtsInfo!.sourceMapFilePath); - assert.isNotFalse(dtsInfo!.sourceMapFilePath); - assert.isDefined(dtsInfo!.sourceMapFilePath); - sourceMapFilePath = dtsInfo!.sourceMapFilePath; + if (!withRefs) { + if (isFirst) { + assert.isNotString(dtsInfo!.sourceMapFilePath); + assert.isNotFalse(dtsInfo!.sourceMapFilePath); + assert.isDefined(dtsInfo!.sourceMapFilePath); + sourceMapFilePath = dtsInfo!.sourceMapFilePath; + } + else { + assert.equal(dtsInfo!.sourceMapFilePath, sourceMapFilePath); + } } - else { - assert.equal(dtsInfo!.sourceMapFilePath, sourceMapFilePath); - } - }); + }, dtsAbsent()); return sourceMapFilePath; } @@ -497,7 +506,7 @@ fn5(); ) { // action verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse, expectedResponseNoDts, verifier }) => { - assert.deepEqual(response, expectedResponseNoDts || expectedResponse, `Failed on ${reqName}`); + assert.deepEqual(response, withRefs ? expectedResponse : expectedResponseNoDts || expectedResponse, `Failed on ${reqName}`); verifyInfosWhenNoDtsFile( session, host, @@ -505,7 +514,8 @@ fn5(); // instead of succeeding to source file and then mapping using project reference (When using usage location) // But watched if sourcemapper is in source project since we need to keep track of dts to update the source mapper for any potential usages verifier.expectedProjectActualFiles.every(f => f.toLowerCase() !== dtsPath), - dependencyTsAndMapOk, + /*dependencyTsOk*/ withRefs || dependencyTsAndMapOk, + /*dependencyMapOk*/ dependencyTsAndMapOk ); }, /*dtsAbsent*/ true); } @@ -513,14 +523,15 @@ fn5(); function verifyScenarioWithChangesWorker( change: (host: TestServerHost, session: TestSession) => void, afterActionDocumentPositionMapperNotEquals: true | undefined, - timeoutBeforeAction: boolean + timeoutBeforeAction: boolean, + useDependencyChange?: boolean ) { const { host, session } = openTsFile(); // Create DocumentPositionMapper firstAction(session); - const dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath)!; - const documentPositionMapper = dependencyMap.documentPositionMapper; + const dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath); + const documentPositionMapper = dependencyMap && dependencyMap.documentPositionMapper; // change change(host, session); @@ -531,21 +542,22 @@ fn5(); } // action - verifyAllFnAction(session, host, afterActionDocumentPositionMapperNotEquals, dependencyMap, documentPositionMapper); + verifyAllFnAction(session, host, afterActionDocumentPositionMapperNotEquals, dependencyMap, documentPositionMapper, useDependencyChange); } function verifyScenarioWithChanges( scenarioName: string, change: (host: TestServerHost, session: TestSession) => void, - afterActionDocumentPositionMapperNotEquals?: true + afterActionDocumentPositionMapperNotEquals?: true, + useDependencyChange?: boolean ) { describe(scenarioName, () => { it("when timeout occurs before request", () => { - verifyScenarioWithChangesWorker(change, afterActionDocumentPositionMapperNotEquals, /*timeoutBeforeAction*/ true); + verifyScenarioWithChangesWorker(change, afterActionDocumentPositionMapperNotEquals, /*timeoutBeforeAction*/ true, useDependencyChange); }); it("when timeout does not occur before request", () => { - verifyScenarioWithChangesWorker(change, afterActionDocumentPositionMapperNotEquals, /*timeoutBeforeAction*/ false); + verifyScenarioWithChangesWorker(change, afterActionDocumentPositionMapperNotEquals, /*timeoutBeforeAction*/ false, useDependencyChange); }); }); } @@ -570,12 +582,12 @@ fn5(); function verifyMainScenarioAndScriptInfoCollectionWithNoMap(session: TestSession, host: TestServerHost, dependencyTsOKInScenario?: true) { // Main scenario action - verifyAllFnActionWithNoMap(session, host, dependencyTsOKInScenario); + verifyAllFnActionWithNoMap(session, host, withRefs || dependencyTsOKInScenario); // Collecting at this point retains dependency.d.ts and map watcher closeFilesForSession([randomFile], session); openFilesForSession([randomFile], session); - verifyInfosWhenNoMapFile(session, host); + verifyInfosWhenNoMapFile(session, host, withRefs); // Closing open file, removes dependencies too closeFilesForSession([...openFiles, randomFile], session); @@ -593,7 +605,8 @@ fn5(); verifyInfosWhenNoDtsFile( session, host, - !!forEach(verifier, v => v.expectedProjectActualFiles.every(f => f.toLowerCase() !== dtsPath)) + !!forEach(verifier, v => v.expectedProjectActualFiles.every(f => f.toLowerCase() !== dtsPath)), + /*dependencyTsOk*/ withRefs ); // Closing open file, removes dependencies too @@ -694,35 +707,26 @@ fn5(); ); if (withRefs) { - it("when defining project source changes", () => { - const { host, session } = openTsFile(); - - // First action - firstAction(session); - - // Make change, without rebuild of solution - if (contains(openInfos, dependencyTs.path)) { - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } + verifyScenarioWithChanges( + "when defining project source changes", + (host, session) => { + // Make change, without rebuild of solution + if (contains(openInfos, dependencyTs.path)) { + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } `} - }); - } - else { - host.writeFile(dependencyTs.path, `function fooBar() { } -${dependencyTs.content}`); - } - host.runQueuedTimeoutCallbacks(); - - for (const v of verifier) { - for (let fn = 1; fn <= 5; fn++) { - const { reqName, request, requestDependencyChange, expectedResponseDependencyChange } = v.actionGetter(fn); - const { response } = session.executeCommandSeq(requestDependencyChange || request); - assert.deepEqual(response, expectedResponseDependencyChange, `Failed on ${reqName}`); + }); } - } - }); + else { + host.writeFile(dependencyTs.path, `function fooBar() { } +${dependencyTs.content}`); + } + }, + /*afterActionDocumentPositionMapperNotEquals*/ undefined, + /*useDepedencyChange*/ true + ); } } @@ -730,13 +734,15 @@ ${dependencyTs.content}`); describe(withRefs ? "when main tsconfig has project reference" : "when main tsconfig doesnt have project reference", () => { const usageVerifier: DocumentPositionMapperVerifier = { openFile: mainTs, - expectedProjectActualFiles: [mainTs.path, libFile.path, mainConfig.path, dtsPath], + expectedProjectActualFiles: withRefs ? + [mainTs.path, libFile.path, mainConfig.path, dependencyTs.path] : + [mainTs.path, libFile.path, mainConfig.path, dtsPath], actionGetter: gotoDefintinionFromMainTs, openFileLastLine: 14 }; describe("from project that uses dependency", () => { const closedInfos = withRefs ? - [dependencyTs.path, dependencyConfig.path, libFile.path, dtsPath, dtsMapLocation] : + [dependencyTs.path, dependencyConfig.path, libFile.path] : [dependencyTs.path, libFile.path, dtsPath, dtsMapLocation]; verifyDocumentPositionMapperUpdates( "can go to definition correctly", @@ -764,7 +770,7 @@ ${dependencyTs.content}`); describe("when opening depedency and usage project", () => { const closedInfos = withRefs ? - [libFile.path, dtsPath, dtsMapLocation, dependencyConfig.path] : + [libFile.path, dependencyConfig.path] : [libFile.path, dtsPath, dtsMapLocation]; verifyDocumentPositionMapperUpdates( "goto Definition in usage and rename locations from defining project", From 2f30add809bbc5988d817896f85ecb93dfec61c4 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 26 Jun 2019 15:29:35 -0700 Subject: [PATCH 09/25] More tests --- .../unittests/tsserver/declarationFileMaps.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/testRunner/unittests/tsserver/declarationFileMaps.ts b/src/testRunner/unittests/tsserver/declarationFileMaps.ts index 0f8af3c414..b060790939 100644 --- a/src/testRunner/unittests/tsserver/declarationFileMaps.ts +++ b/src/testRunner/unittests/tsserver/declarationFileMaps.ts @@ -199,7 +199,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", () => { @@ -470,6 +470,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, @@ -477,13 +484,6 @@ namespace ts.projectSystem { isWriteAccess: false, textSpan: { start: 0, length: 1 }, }, - makeReferenceEntry({ - file: aTs, - text: "f", - options: { index: 1 }, - contextText: "function f() {}", - isDefinition: true - }) ], } ]); From da9260c01305bc4f91193f787c868f0def6c0f48 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 26 Jun 2019 15:57:22 -0700 Subject: [PATCH 10/25] Create original project when location is in source of project reference redirect --- src/compiler/program.ts | 2 +- src/compiler/utilities.ts | 4 ++++ src/server/editorServices.ts | 7 +++++-- src/server/project.ts | 18 +++++++++++++++--- src/server/session.ts | 6 ++++-- src/services/sourcemaps.ts | 7 ++++--- .../tsserver/events/projectLoading.ts | 2 +- 7 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 9a4e26a290..2ddf191a32 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -814,7 +814,7 @@ namespace ts { let projectReferenceRedirects: Map | undefined; let mapFromFileToProjectReferenceRedirects: Map | undefined; let mapFromToProjectReferenceRedirectSource: Map | undefined; - const useSourceOfReference = host.useSourceInsteadOfReferenceRedirect && host.useSourceInsteadOfReferenceRedirect(); + const useSourceOfReference = useSourceInsteadOfReferenceRedirect(host); const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options); const structuralIsReused = tryReuseStructureFromOldProgram(); diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 0e2488a023..ecf7d431a2 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -4614,6 +4614,10 @@ namespace ts { return false; } } + + export function useSourceInsteadOfReferenceRedirect(host: { useSourceInsteadOfReferenceRedirect?(): boolean; }) { + return host.useSourceInsteadOfReferenceRedirect && host.useSourceInsteadOfReferenceRedirect(); + } } namespace ts { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 8c7462d857..e379d1fda2 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -2570,7 +2570,9 @@ namespace ts.server { /*@internal*/ getOriginalLocationEnsuringConfiguredProject(project: Project, location: DocumentPosition): DocumentPosition | undefined { - const originalLocation = project.getSourceMapper().tryGetSourcePosition(location); + const originalLocation = useSourceInsteadOfReferenceRedirect(project) && project.getResolvedProjectReferenceToRedirect(location.fileName) ? + location : + project.getSourceMapper().tryGetSourcePosition(location); if (!originalLocation) return undefined; const { fileName } = originalLocation; @@ -2581,7 +2583,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); diff --git a/src/server/project.ts b/src/server/project.ts index 8f8e6e5da8..c6c8ab84b2 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -196,6 +196,14 @@ namespace ts.server { /*@internal*/ originalConfiguredProjects: Map | undefined; + /*@internal*/ + useSourceInsteadOfReferenceRedirect?: () => boolean; + + /*@internal*/ + getResolvedProjectReferenceToRedirect(_fileName: string): ResolvedProjectReference | undefined { + return undefined; + } + private readonly cancellationToken: ThrottledCancellationToken; public isNonTsProject() { @@ -1526,9 +1534,7 @@ namespace ts.server { } /* @internal */ - useSourceInsteadOfReferenceRedirect() { - return !!this.languageServiceEnabled; - } + useSourceInsteadOfReferenceRedirect = () => !!this.languageServiceEnabled; fileExists(file: string): boolean { // Project references go to source file instead of .d.ts file @@ -1590,6 +1596,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 | undefined) { const host = this.projectService.host; diff --git a/src/server/session.ts b/src/server/session.ts index 5064b52956..792ec2cbce 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -443,7 +443,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 = useSourceInsteadOfReferenceRedirect(definingProject) && definingProject.getResolvedProjectReferenceToRedirect(definition.fileName) ? + definition : + definingProject.getLanguageService().getSourceMapper().tryGetGeneratedPosition(definition); return mappedDefinition && project.containsFile(toNormalizedPath(mappedDefinition.fileName)) ? mappedDefinition : undefined; } @@ -472,7 +474,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; } diff --git a/src/services/sourcemaps.ts b/src/services/sourcemaps.ts index de590d4e5b..c4c14e5868 100644 --- a/src/services/sourcemaps.ts +++ b/src/services/sourcemaps.ts @@ -72,9 +72,10 @@ namespace ts { const program = host.getProgram()!; // If this is source file of project reference source (instead of redirect) there is no generated position - if (host.useSourceInsteadOfReferenceRedirect && - host.useSourceInsteadOfReferenceRedirect() && - program.getResolvedProjectReferenceToRedirect(sourceFile.fileName)) return undefined; + if (useSourceInsteadOfReferenceRedirect(host) && + program.getResolvedProjectReferenceToRedirect(sourceFile.fileName)) { + return undefined; + } const options = program.getCompilerOptions(); const outPath = options.outFile || options.out; diff --git a/src/testRunner/unittests/tsserver/events/projectLoading.ts b/src/testRunner/unittests/tsserver/events/projectLoading.ts index 7a881ff138..53fe28240f 100644 --- a/src/testRunner/unittests/tsserver/events/projectLoading.ts +++ b/src/testRunner/unittests/tsserver/events/projectLoading.ts @@ -110,7 +110,7 @@ namespace ts.projectSystem { 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}`); + verifyEvent(project, `Creating project for original file: ${aTs.path}`); }); describe("with external projects and config files ", () => { From 75bd3cd9be28686c492d7028ed828fce7f9bcef9 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 27 Jun 2019 12:12:02 -0700 Subject: [PATCH 11/25] Fix more tests --- src/testRunner/unittests/tsbuildWatchMode.ts | 191 +++++++++++-------- 1 file changed, 108 insertions(+), 83 deletions(-) diff --git a/src/testRunner/unittests/tsbuildWatchMode.ts b/src/testRunner/unittests/tsbuildWatchMode.ts index a630e28f1d..6a577888c7 100644 --- a/src/testRunner/unittests/tsbuildWatchMode.ts +++ b/src/testRunner/unittests/tsbuildWatchMode.ts @@ -681,9 +681,9 @@ 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 expectedProgramFiles = [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, logicIndexDts]; + const expectedProjectFiles = [libFile, ...tests, ...logic.slice(1), ...core.slice(1, core.length - 1)].map(f => f.path); function createSolutionAndWatchMode() { return createSolutionAndWatchModeOfProject(allFiles, projectsLocation, `${project}/${SubProject.tests}`, tests[0].path, getOutputFileStamps); @@ -694,12 +694,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) => void, - expectedFilesAfterEdit: ReadonlyArray + expectedProgramFilesAfterEdit: ReadonlyArray, + expectedProjectFilesAfterEdit: ReadonlyArray ) { it("with tsc-watch", () => { const { host, solutionBuilder, watch } = createSolutionAndWatchMode(); @@ -708,7 +715,7 @@ let x: string = 10;`); host.checkTimeoutQueueLengthAndRun(1); checkOutputErrorsIncremental(host, emptyArray); - checkProgramActualFiles(watch(), expectedFilesAfterEdit); + checkProgramActualFiles(watch(), expectedProgramFilesAfterEdit); }); @@ -718,7 +725,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]); }); } @@ -748,7 +755,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", () => { @@ -758,7 +765,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", () => { @@ -769,7 +776,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); }); }); @@ -859,7 +866,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; @@ -897,22 +906,29 @@ export function gfoo() { } function verifyProject(host: TsBuildWatchSystem, service: projectSystem.TestProjectService, orphanInfos?: ReadonlyArray) { - verifyServerState(host, service, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos); + verifyServerState({ host, service, expectedProjectFiles, expectedProjectWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos }); } - function verifyServerState( - host: TsBuildWatchSystem, - service: projectSystem.TestProjectService, - expectedProgramFiles: ReadonlyArray, - expectedWatchedFiles: ReadonlyArray, - expectedWatchedDirectoriesRecursive: ReadonlyArray, - orphanInfos?: ReadonlyArray) { - 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: ReadonlyArray; + expectedProjectWatchedFiles: ReadonlyArray; + expectedWatchedDirectoriesRecursive: ReadonlyArray; + orphanInfos?: ReadonlyArray; + } + 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); } @@ -920,16 +936,20 @@ export function gfoo() { verifyWatchesOfProject(host, watchedFiles, expectedWatchedDirectoriesRecursive, expectedWatchedDirectories); } - function verifyScenario( - edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder) => void, - expectedEditErrors: ReadonlyArray, - expectedProgramFiles: ReadonlyArray, - expectedWatchedFiles: ReadonlyArray, - expectedWatchedDirectoriesRecursive: ReadonlyArray, - dependencies: ReadonlyArray<[string, ReadonlyArray]>, - revert?: (host: TsBuildWatchSystem) => void, - orphanInfosAfterEdit?: ReadonlyArray, - orphanInfosAfterRevert?: ReadonlyArray) { + interface VerifyScenario { + edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder) => void; + expectedEditErrors: ReadonlyArray; + expectedProgramFiles: ReadonlyArray; + expectedProjectFiles: ReadonlyArray; + expectedWatchedFiles: ReadonlyArray; + expectedProjectWatchedFiles: ReadonlyArray; + expectedWatchedDirectoriesRecursive: ReadonlyArray; + dependencies: ReadonlyArray<[string, ReadonlyArray]>; + revert?: (host: TsBuildWatchSystem) => void; + orphanInfosAfterEdit?: ReadonlyArray; + orphanInfosAfterRevert?: ReadonlyArray; + } + function verifyScenario({ edit, expectedEditErrors, expectedProgramFiles, expectedProjectFiles, expectedWatchedFiles, expectedProjectWatchedFiles, expectedWatchedDirectoriesRecursive, dependencies, revert, orphanInfosAfterEdit, orphanInfosAfterRevert }: VerifyScenario) { it("with tsc-watch", () => { const { host, solutionBuilder, watch } = createSolutionAndWatchMode(); @@ -956,7 +976,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); @@ -981,20 +1001,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", () => { @@ -1003,30 +1024,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", () => { @@ -1035,82 +1058,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), + }); }); } From f72af3be60688fa30a2e918e8dc86c1fad882b8a Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 28 Jun 2019 15:49:48 -0700 Subject: [PATCH 12/25] Verify the scenarios when d.ts directory of dependency doesnt exist --- src/compiler/program.ts | 14 ++++-- src/compiler/types.ts | 9 ++-- src/server/project.ts | 45 ++++++++++++++++--- src/services/services.ts | 4 +- src/services/types.ts | 2 +- .../unittests/tsserver/projectReferences.ts | 30 +++++++++++++ 6 files changed, 88 insertions(+), 16 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 27f5d25c84..37c2d6a9c8 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -826,8 +826,11 @@ namespace ts { if (!resolvedProjectReferences) { resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile); } - if (host.setGetSourceOfProjectReferenceRedirect) { - host.setGetSourceOfProjectReferenceRedirect(getSourceOfProjectReferenceRedirect); + if (host.setResolvedProjectReferenceCallbacks) { + host.setResolvedProjectReferenceCallbacks({ + getSourceOfProjectReferenceRedirect, + forEachResolvedProjectReference + }); } if (rootNames.length) { for (const parsedRef of resolvedProjectReferences) { @@ -1226,8 +1229,11 @@ namespace ts { } if (projectReferences) { resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile); - if (host.setGetSourceOfProjectReferenceRedirect) { - host.setGetSourceOfProjectReferenceRedirect(getSourceOfProjectReferenceRedirect); + if (host.setResolvedProjectReferenceCallbacks) { + host.setResolvedProjectReferenceCallbacks({ + getSourceOfProjectReferenceRedirect, + forEachResolvedProjectReference + }); } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 79a92cfa31..3cf506b0d5 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5166,7 +5166,7 @@ namespace ts { /* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean; createHash?(data: string): string; getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined; - /* @internal */ setGetSourceOfProjectReferenceRedirect?(getSource: GetSourceOfProjectReferenceRedirect): void; + /* @internal */ setResolvedProjectReferenceCallbacks?(callbacks: ResolvedProjectReferenceCallbacks): void; /* @internal */ useSourceInsteadOfReferenceRedirect?(): 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 @@ -5175,10 +5175,13 @@ namespace ts { /** true if --out otherwise source file name */ /*@internal*/ - export type SourceOfProjectReferenceRedirect = string | true ; + export type SourceOfProjectReferenceRedirect = string | true; /*@internal*/ - export type GetSourceOfProjectReferenceRedirect = (fileName: string) => SourceOfProjectReferenceRedirect | undefined; + interface ResolvedProjectReferenceCallbacks { + getSourceOfProjectReferenceRedirect(fileName: string): SourceOfProjectReferenceRedirect | undefined; + forEachResolvedProjectReference(cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined): T | undefined; + } /* @internal */ export const enum TransformFlags { diff --git a/src/server/project.ts b/src/server/project.ts index c6c8ab84b2..04a5b91531 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1482,7 +1482,8 @@ namespace ts.server { configFileWatcher: FileWatcher | undefined; private directoriesWatchedForWildcards: Map | undefined; readonly canonicalConfigFilePath: NormalizedPath; - private getSourceOfProjectReferenceRedirect: GetSourceOfProjectReferenceRedirect | undefined; + private projectReferenceCallbacks: ResolvedProjectReferenceCallbacks | undefined; + private mapOfDeclarationDirectories: Map | undefined; /* @internal */ pendingReload: ConfigFileProgramReloadLevel | undefined; @@ -1529,8 +1530,8 @@ namespace ts.server { } /* @internal */ - setGetSourceOfProjectReferenceRedirect(getSource: GetSourceOfProjectReferenceRedirect) { - this.getSourceOfProjectReferenceRedirect = getSource; + setResolvedProjectReferenceCallbacks(projectReferenceCallbacks: ResolvedProjectReferenceCallbacks) { + this.projectReferenceCallbacks = projectReferenceCallbacks; } /* @internal */ @@ -1538,13 +1539,42 @@ namespace ts.server { fileExists(file: string): boolean { // Project references go to source file instead of .d.ts file - if (this.languageServiceEnabled && this.getSourceOfProjectReferenceRedirect) { - const source = this.getSourceOfProjectReferenceRedirect(file); + if (this.useSourceInsteadOfReferenceRedirect() && this.projectReferenceCallbacks) { + const source = this.projectReferenceCallbacks.getSourceOfProjectReferenceRedirect(file); if (source) return isString(source) ? super.fileExists(source) : true; } return super.fileExists(file); } + directoryExists(path: string): boolean { + if (super.directoryExists(path)) return true; + if (!this.useSourceInsteadOfReferenceRedirect() || !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. @@ -1553,6 +1583,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: @@ -1567,7 +1599,6 @@ namespace ts.server { default: result = super.updateGraph(); } - this.getSourceOfProjectReferenceRedirect = undefined; this.projectService.sendProjectLoadingFinishEvent(this); this.projectService.sendProjectTelemetry(this); return result; @@ -1684,6 +1715,8 @@ namespace ts.server { this.stopWatchingWildCards(); this.projectErrors = undefined; this.configFileSpecs = undefined; + this.projectReferenceCallbacks = undefined; + this.mapOfDeclarationDirectories = undefined; super.close(); } diff --git a/src/services/services.ts b/src/services/services.ts index aa6ea11922..7de7934653 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1246,8 +1246,8 @@ namespace ts { return host.resolveTypeReferenceDirectives!(typeReferenceDirectiveNames, containingFile, redirectedReference); }; } - if (host.setGetSourceOfProjectReferenceRedirect) { - compilerHost.setGetSourceOfProjectReferenceRedirect = getSource => host.setGetSourceOfProjectReferenceRedirect!(getSource); + if (host.setResolvedProjectReferenceCallbacks) { + compilerHost.setResolvedProjectReferenceCallbacks = callbacks => host.setResolvedProjectReferenceCallbacks!(callbacks); } if (host.useSourceInsteadOfReferenceRedirect) { compilerHost.useSourceInsteadOfReferenceRedirect = () => host.useSourceInsteadOfReferenceRedirect!(); diff --git a/src/services/types.ts b/src/services/types.ts index 3c9509ca5e..b4fc25e587 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -237,7 +237,7 @@ namespace ts { /* @internal */ getSourceFileLike?(fileName: string): SourceFileLike | undefined; /* @internal */ - setGetSourceOfProjectReferenceRedirect?(getSource: GetSourceOfProjectReferenceRedirect): void; + setResolvedProjectReferenceCallbacks?(callbacks: ResolvedProjectReferenceCallbacks): void; /* @internal */ useSourceInsteadOfReferenceRedirect?(): boolean; } diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index 5320cfe13b..0b8b074f8e 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -727,6 +727,36 @@ ${dependencyTs.content}`); /*afterActionDocumentPositionMapperNotEquals*/ undefined, /*useDepedencyChange*/ true ); + + it("when d.ts file is not generated", () => { + const host = createServerHost(files); + const session = createSession(host); + openFilesForSession([...openFiles, randomFile], session); + + const expectedClosedInfos = closedInfos.filter(f => f.toLowerCase() !== dtsPath && f.toLowerCase() !== dtsMapPath); + // If closed infos includes dts and dtsMap, watch dts since its not present + const expectedWatchedFiles = closedInfos.length === expectedClosedInfos.length ? + otherWatchedFiles : + otherWatchedFiles.concat(dtsPath); + // Main scenario action + verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse }) => { + assert.deepEqual(response, expectedResponse, `Failed on ${reqName}`); + verifyInfosWithRandom(session, host, openInfos, expectedClosedInfos, expectedWatchedFiles); + verifyDocumentPositionMapper(session, /*dependencyMap*/ undefined, /*documentPositionMapper*/ undefined); + }, /*dtsAbsent*/ true); + checkProject(session); + + // Collecting at this point retains dependency.d.ts and map + closeFilesForSession([randomFile], session); + openFilesForSession([randomFile], session); + verifyInfosWithRandom(session, host, openInfos, expectedClosedInfos, expectedWatchedFiles); + verifyDocumentPositionMapper(session, /*dependencyMap*/ undefined, /*documentPositionMapper*/ undefined); + + // Closing open file, removes dependencies too + closeFilesForSession([...openFiles, randomFile], session); + openFilesForSession([randomFile], session); + verifyOnlyRandomInfos(session, host); + }); } } From f9e4b91203a0bcbddcca2acee442bb4f5ca0a869 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 1 Jul 2019 13:37:35 -0700 Subject: [PATCH 13/25] Fix incorrectly exported type --- src/compiler/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 3cf506b0d5..ff0aba6aca 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5178,7 +5178,7 @@ namespace ts { export type SourceOfProjectReferenceRedirect = string | true; /*@internal*/ - interface ResolvedProjectReferenceCallbacks { + export interface ResolvedProjectReferenceCallbacks { getSourceOfProjectReferenceRedirect(fileName: string): SourceOfProjectReferenceRedirect | undefined; forEachResolvedProjectReference(cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined): T | undefined; } From f7ea0bab60d4198c04f81de624ea61b6d75bcc9c Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 1 Jul 2019 14:29:32 -0700 Subject: [PATCH 14/25] Refactoring --- src/compiler/program.ts | 7 ++++++- src/compiler/types.ts | 2 ++ src/compiler/utilities.ts | 4 ---- src/server/editorServices.ts | 2 +- src/server/project.ts | 8 +++++--- src/server/session.ts | 2 +- src/services/services.ts | 1 - src/services/sourcemaps.ts | 4 +--- 8 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 9da4b2efe0..78e5f31487 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -814,7 +814,7 @@ namespace ts { let projectReferenceRedirects: Map | undefined; let mapFromFileToProjectReferenceRedirects: Map | undefined; let mapFromToProjectReferenceRedirectSource: Map | undefined; - const useSourceOfReference = useSourceInsteadOfReferenceRedirect(host); + const useSourceOfReference = !!host.useSourceInsteadOfReferenceRedirect && host.useSourceInsteadOfReferenceRedirect(); const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options); const structuralIsReused = tryReuseStructureFromOldProgram(); @@ -964,6 +964,7 @@ namespace ts { getResolvedProjectReferenceToRedirect, getResolvedProjectReferenceByPath, forEachResolvedProjectReference, + isSourceOfProjectReferenceRedirect, emitBuildInfo }; @@ -2496,6 +2497,10 @@ namespace ts { return mapFromToProjectReferenceRedirectSource.get(toPath(file)); } + function isSourceOfProjectReferenceRedirect(fileName: string) { + return useSourceOfReference && !!getResolvedProjectReferenceToRedirect(fileName); + } + function forEachProjectReference( projectReferences: ReadonlyArray | undefined, resolvedProjectReferences: ReadonlyArray | undefined, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ff0aba6aca..16ea47beec 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2993,6 +2993,7 @@ namespace ts { /*@internal*/ getResolvedProjectReferenceToRedirect(fileName: string): ResolvedProjectReference | undefined; /*@internal*/ forEachResolvedProjectReference(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; } @@ -3090,6 +3091,7 @@ namespace ts { getSourceFile(fileName: string): SourceFile | undefined; getResolvedTypeReferenceDirectives(): ReadonlyMap; getProjectReferenceRedirect(fileName: string): string | undefined; + isSourceOfProjectReferenceRedirect(fileName: string): boolean; readonly redirectTargetsMap: RedirectTargetsMap; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 47877e1b27..562d066b64 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -4616,10 +4616,6 @@ namespace ts { return false; } } - - export function useSourceInsteadOfReferenceRedirect(host: { useSourceInsteadOfReferenceRedirect?(): boolean; }) { - return host.useSourceInsteadOfReferenceRedirect && host.useSourceInsteadOfReferenceRedirect(); - } } namespace ts { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index e379d1fda2..6fea0bc417 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -2570,7 +2570,7 @@ namespace ts.server { /*@internal*/ getOriginalLocationEnsuringConfiguredProject(project: Project, location: DocumentPosition): DocumentPosition | undefined { - const originalLocation = useSourceInsteadOfReferenceRedirect(project) && project.getResolvedProjectReferenceToRedirect(location.fileName) ? + const originalLocation = project.isSourceOfProjectReferenceRedirect(location.fileName) ? location : project.getSourceMapper().tryGetSourcePosition(location); if (!originalLocation) return undefined; diff --git a/src/server/project.ts b/src/server/project.ts index 4e038b9477..cd5a3fec85 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -196,9 +196,6 @@ namespace ts.server { /*@internal*/ originalConfiguredProjects: Map | undefined; - /*@internal*/ - useSourceInsteadOfReferenceRedirect?: () => boolean; - /*@internal*/ getResolvedProjectReferenceToRedirect(_fileName: string): ResolvedProjectReference | undefined { return undefined; @@ -1231,6 +1228,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 | undefined) { const host = this.projectService.host; diff --git a/src/server/session.ts b/src/server/session.ts index 088a3a1a24..ff339b06a8 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -443,7 +443,7 @@ 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 = useSourceInsteadOfReferenceRedirect(definingProject) && definingProject.getResolvedProjectReferenceToRedirect(definition.fileName) ? + const mappedDefinition = definingProject.isSourceOfProjectReferenceRedirect(definition.fileName) ? definition : definingProject.getLanguageService().getSourceMapper().tryGetGeneratedPosition(definition); return mappedDefinition && project.containsFile(toNormalizedPath(mappedDefinition.fileName)) ? mappedDefinition : undefined; diff --git a/src/services/services.ts b/src/services/services.ts index 7de7934653..2f9ba6eec9 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1151,7 +1151,6 @@ namespace ts { fileExists: maybeBind(host, host.fileExists), readFile: maybeBind(host, host.readFile), getDocumentPositionMapper: maybeBind(host, host.getDocumentPositionMapper), - useSourceInsteadOfReferenceRedirect: maybeBind(host, host.useSourceInsteadOfReferenceRedirect), getSourceFileLike: maybeBind(host, host.getSourceFileLike), log }); diff --git a/src/services/sourcemaps.ts b/src/services/sourcemaps.ts index c4c14e5868..6ab656b423 100644 --- a/src/services/sourcemaps.ts +++ b/src/services/sourcemaps.ts @@ -17,7 +17,6 @@ namespace ts { readFile?(path: string, encoding?: string): string | undefined; getSourceFileLike?(fileName: string): SourceFileLike | undefined; getDocumentPositionMapper?(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined; - /* @internal */ useSourceInsteadOfReferenceRedirect?(): boolean; log(s: string): void; } @@ -72,8 +71,7 @@ namespace ts { const program = host.getProgram()!; // If this is source file of project reference source (instead of redirect) there is no generated position - if (useSourceInsteadOfReferenceRedirect(host) && - program.getResolvedProjectReferenceToRedirect(sourceFile.fileName)) { + if (program.isSourceOfProjectReferenceRedirect(sourceFile.fileName)) { return undefined; } From b5737fc535701caff9b197b3cba6dd4c832e745f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 2 Jul 2019 16:29:09 -0700 Subject: [PATCH 15/25] Refactor tests so its easy to edit and reason about them --- src/harness/virtualFileSystemWithWatch.ts | 4 +- src/testRunner/unittests/tsserver/helpers.ts | 4 +- .../unittests/tsserver/projectReferences.ts | 1325 +++++++++++------ 3 files changed, 837 insertions(+), 496 deletions(-) diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index f49f15cadc..9e615a0263 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -196,8 +196,8 @@ interface Array {}` } } - 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): void; diff --git a/src/testRunner/unittests/tsserver/helpers.ts b/src/testRunner/unittests/tsserver/helpers.ts index 1c2383ab00..1c6bd4e9f5 100644 --- a/src/testRunner/unittests/tsserver/helpers.ts +++ b/src/testRunner/unittests/tsserver/helpers.ts @@ -488,8 +488,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: ReadonlyArray) { - checkArray("ScriptInfos files", arrayFrom(projectService.filenameToScriptInfo.values(), info => info.fileName), expectedFiles); + export function checkScriptInfos(projectService: server.ProjectService, expectedFiles: ReadonlyArray, additionInfo?: string) { + checkArray(`ScriptInfos files: ${additionInfo || ""}`, arrayFrom(projectService.filenameToScriptInfo.values(), info => info.fileName), expectedFiles); } export function protocolLocationFromSubstring(str: string, substring: string): protocol.Location { diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index 1eb7e8ecd1..c5712df2e1 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -150,66 +150,17 @@ fn5(); const files = [dependencyTs, dependencyConfig, mainTs, mainConfig, libFile, randomFile, randomConfig]; - function verifyScriptInfos(session: TestSession, host: TestServerHost, openInfos: ReadonlyArray, closedInfos: ReadonlyArray, otherWatchedFiles: ReadonlyArray) { - checkScriptInfos(session.getProjectService(), openInfos.concat(closedInfos)); - checkWatchedFiles(host, closedInfos.concat(otherWatchedFiles).map(f => f.toLowerCase())); + function verifyScriptInfos(session: TestSession, host: TestServerHost, openInfos: ReadonlyArray, closedInfos: ReadonlyArray, otherWatchedFiles: ReadonlyArray, additionalInfo: string) { + checkScriptInfos(session.getProjectService(), openInfos.concat(closedInfos), additionalInfo); + checkWatchedFiles(host, closedInfos.concat(otherWatchedFiles).map(f => f.toLowerCase()), additionalInfo); } - function verifyInfosWithRandom(session: TestSession, host: TestServerHost, openInfos: ReadonlyArray, closedInfos: ReadonlyArray, otherWatchedFiles: ReadonlyArray) { - verifyScriptInfos(session, host, openInfos.concat(randomFile.path), closedInfos, otherWatchedFiles.concat(randomConfig.path)); + function verifyInfosWithRandom(session: TestSession, host: TestServerHost, openInfos: ReadonlyArray, closedInfos: ReadonlyArray, otherWatchedFiles: ReadonlyArray, reqName: string) { + verifyScriptInfos(session, host, openInfos.concat(randomFile.path), closedInfos, otherWatchedFiles.concat(randomConfig.path), reqName); } function verifyOnlyRandomInfos(session: TestSession, host: TestServerHost) { - verifyScriptInfos(session, host, [randomFile.path], [libFile.path], [randomConfig.path]); - } - - // Returns request and expected Response, expected response when no map file - interface SessionAction { - reqName: string; - request: Partial; - expectedResponse: Response; - expectedResponseNoMap?: Response; - expectedResponseNoDts?: Response; - requestDependencyChange?: Partial; - expectedResponseDependencyChange: Response; - } - function gotoDefintinionFromMainTs(fn: number): SessionAction { - const textSpan = usageSpan(fn); - const definition: protocol.FileSpan = { file: dependencyTs.path, ...declarationSpan(fn) }; - const declareSpaceLength = "declare ".length; - return { - reqName: "goToDef", - request: { - command: protocol.CommandTypes.DefinitionAndBoundSpan, - arguments: { file: mainTs.path, ...textSpan.start } - }, - expectedResponse: { - // To dependency - definitions: [definition], - textSpan - }, - expectedResponseNoMap: { - // To the dts - definitions: [{ - file: dtsPath, - start: { line: fn, offset: definition.start.offset + declareSpaceLength }, - end: { line: fn, offset: definition.end.offset + declareSpaceLength }, - contextStart: { line: fn, offset: 1 }, - contextEnd: { line: fn, offset: 37 } - }], - textSpan - }, - expectedResponseNoDts: { - // To import declaration - definitions: [{ file: mainTs.path, ...importSpan(fn) }], - textSpan - }, - expectedResponseDependencyChange: { - // Definition on fn + 1 line - definitions: [{ file: dependencyTs.path, ...declarationSpan(fn + 1) }], - textSpan - } - }; + verifyScriptInfos(session, host, [randomFile.path], [libFile.path], [randomConfig.path], "Random"); } function declarationSpan(fn: number): protocol.TextSpanWithContext { @@ -232,11 +183,93 @@ fn5(); return { start: { line: fn + 8, offset: 1 }, end: { line: fn + 8, offset: 4 } }; } - function renameFromDependencyTs(fn: number): SessionAction { + function goToDefFromMainTs(fn: number): Action { + const textSpan = usageSpan(fn); + const definition: protocol.FileSpan = { file: dependencyTs.path, ...declarationSpan(fn) }; + return { + reqName: "goToDef", + request: { + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: { file: mainTs.path, ...textSpan.start } + }, + expectedResponse: { + // To dependency + definitions: [definition], + textSpan + } + }; + } + + function goToDefFromMainTsWithNoMap(fn: number): Action { + const textSpan = usageSpan(fn); + const definition = declarationSpan(fn); + const declareSpaceLength = "declare ".length; + return { + reqName: "goToDef", + request: { + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: { file: mainTs.path, ...textSpan.start } + }, + expectedResponse: { + // To the dts + definitions: [{ + file: dtsPath, + start: { line: fn, offset: definition.start.offset + declareSpaceLength }, + end: { line: fn, offset: definition.end.offset + declareSpaceLength }, + contextStart: { line: fn, offset: 1 }, + contextEnd: { line: fn, offset: 37 } + }], + textSpan + } + }; + } + + function goToDefFromMainTsWithNoDts(fn: number): Action { + const textSpan = usageSpan(fn); + return { + reqName: "goToDef", + request: { + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: { file: mainTs.path, ...textSpan.start } + }, + expectedResponse: { + // To import declaration + definitions: [{ file: mainTs.path, ...importSpan(fn) }], + textSpan + } + }; + } + + function goToDefFromMainTsWithDependencyChange(fn: number): Action { + const textSpan = usageSpan(fn); + return { + reqName: "goToDef", + request: { + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: { file: mainTs.path, ...textSpan.start } + }, + expectedResponse: { + // Definition on fn + 1 line + definitions: [{ file: dependencyTs.path, ...declarationSpan(fn + 1) }], + textSpan + } + }; + } + + function goToDefFromMainTsProjectInfoVerifier(withRefs: boolean): ProjectInfoVerifier { + return { + openFile: mainTs, + openFileLastLine: 14, + configFile: mainConfig, + expectedProjectActualFiles: withRefs ? + [mainTs.path, libFile.path, mainConfig.path, dependencyTs.path] : + [mainTs.path, libFile.path, mainConfig.path, dtsPath] + }; + } + + function renameFromDependencyTs(fn: number): Action { const defSpan = declarationSpan(fn); const { contextStart: _, contextEnd: _1, ...triggerSpan } = defSpan; - const defSpanPlusOne = declarationSpan(fn + 1); - const { contextStart: _2, contextEnd: _3, ...triggerSpanPlusOne } = defSpanPlusOne; return { reqName: "rename", request: { @@ -256,30 +289,37 @@ fn5(); locs: [ { file: dependencyTs.path, locs: [defSpan] } ] - }, - requestDependencyChange: { - command: protocol.CommandTypes.Rename, - arguments: { file: dependencyTs.path, ...triggerSpanPlusOne.start } - }, - expectedResponseDependencyChange: { - info: { - canRename: true, - fileToRename: undefined, - displayName: `fn${fn}`, - fullDisplayName: `"${dependecyLocation}/FnS".fn${fn}`, - kind: ScriptElementKind.functionElement, - kindModifiers: "export", - triggerSpan: triggerSpanPlusOne - }, - locs: [ - { file: dependencyTs.path, locs: [defSpanPlusOne] } - ] } }; } - function renameFromDependencyTsWithBothProjectsOpen(fn: number): SessionAction { - const { reqName, request, expectedResponse, expectedResponseDependencyChange, requestDependencyChange } = renameFromDependencyTs(fn); + function renameFromDependencyTsWithDependencyChange(fn: number): Action { + const { expectedResponse: { info, locs }, ...rest } = renameFromDependencyTs(fn + 1); + + return { + ...rest, + expectedResponse: { + info: { + ...info as protocol.RenameInfoSuccess, + displayName: `fn${fn}`, + fullDisplayName: `"${dependecyLocation}/FnS".fn${fn}`, + }, + locs + } + }; + } + + function renameFromDependencyTsProjectInfoVerifier(): ProjectInfoVerifier { + return { + openFile: dependencyTs, + openFileLastLine: 6, + configFile: dependencyConfig, + expectedProjectActualFiles: [dependencyTs.path, libFile.path, dependencyConfig.path] + }; + } + + function renameFromDependencyTsWithBothProjectsOpen(fn: number): Action { + const { reqName, request, expectedResponse } = renameFromDependencyTs(fn); const { info, locs } = expectedResponse; return { reqName, @@ -296,15 +336,20 @@ fn5(); ] } ] - }, - // Only dependency result - expectedResponseNoMap: expectedResponse, - expectedResponseNoDts: expectedResponse, - requestDependencyChange, - expectedResponseDependencyChange: { - info: expectedResponseDependencyChange.info, + } + }; + } + + function renameFromDependencyTsWithBothProjectsOpenWithDependencyChange(fn: number): Action { + const { reqName, request, expectedResponse, } = renameFromDependencyTsWithDependencyChange(fn); + const { info, locs } = expectedResponse; + return { + reqName, + request, + expectedResponse: { + info, locs: [ - expectedResponseDependencyChange.locs[0], + locs[0], { file: mainTs.path, locs: [ @@ -317,401 +362,453 @@ fn5(); }; } - // Returns request and expected Response - type SessionActionGetter = (fn: number) => SessionAction; - // Open File, expectedProjectActualFiles, actionGetter, openFileLastLine - interface DocumentPositionMapperVerifier { - openFile: File; - expectedProjectActualFiles: ReadonlyArray; - actionGetter: SessionActionGetter; - openFileLastLine: number; + interface Action { + reqName: string; + request: Partial; + expectedResponse: Response; + } + interface ActionInfo { + action: (fn: number) => Action; + closedInfos: () => readonly string[]; + otherWatchedFiles: () => readonly string[]; + expectsDts: boolean; + expectsMap: boolean; + } + type ActionKey = keyof ActionInfoVerifier; + type ActionInfoGetterFn = () => ActionInfo; + type ActionInfoGetter = ActionInfoGetterFn | ActionKey; + interface ProjectInfoVerifier { + openFile: File; + openFileLastLine: number; + configFile: File; + expectedProjectActualFiles: readonly string[]; + } + interface ActionInfoVerifier { + main: ActionInfoGetter; + noMap: ActionInfoGetter; + mapFileCreated: ActionInfoGetter; + mapFileDeleted: ActionInfoGetter; + noDts: ActionInfoGetter; + dtsFileCreated: ActionInfoGetter; + dtsFileDeleted: ActionInfoGetter; + dependencyChange: ActionInfoGetter; + noBuild: ActionInfoGetter; + } + interface DocumentPositionMapperVerifier extends ProjectInfoVerifier, ActionInfoVerifier { } - function verifyDocumentPositionMapperUpdates( - mainScenario: string, - verifier: ReadonlyArray, - closedInfos: ReadonlyArray, - withRefs: boolean) { - const openFiles = verifier.map(v => v.openFile); - const expectedProjectActualFiles = verifier.map(v => v.expectedProjectActualFiles); - const openFileLastLines = verifier.map(v => v.openFileLastLine); + interface VerifierAndWithRefs { + withRefs: boolean; + verifier: (withRefs: boolean) => readonly DocumentPositionMapperVerifier[]; + } - const configFiles = openFiles.map(openFile => `${getDirectoryPath(openFile.path)}/tsconfig.json`); - const openInfos = openFiles.map(f => f.path); - // When usage and dependency are used, dependency config is part of closedInfo so ignore - const otherWatchedFiles = withRefs && verifier.length > 1 ? [configFiles[0]] : configFiles; - function openTsFile(onHostCreate?: (host: TestServerHost) => void) { - const host = createHost(files, [mainConfig.path]); - if (!withRefs) { - // Erase project reference - host.writeFile(mainConfig.path, JSON.stringify({ - compilerOptions: { composite: true, declarationMap: true } - })); - } - if (onHostCreate) { - onHostCreate(host); - } - const session = createSession(host); - openFilesForSession([...openFiles, randomFile], session); - return { host, session }; + function openFiles(verifiers: readonly DocumentPositionMapperVerifier[]) { + return verifiers.map(v => v.openFile); + } + interface OpenTsFile extends VerifierAndWithRefs { + onHostCreate?: (host: TestServerHost) => void; + } + function openTsFile({ withRefs, verifier, onHostCreate }: OpenTsFile) { + const host = createHost(files, [mainConfig.path]); + if (!withRefs) { + // Erase project reference + host.writeFile(mainConfig.path, JSON.stringify({ + compilerOptions: { composite: true, declarationMap: true } + })); } - - function checkProject(session: TestSession, noDts?: true) { - const service = session.getProjectService(); - checkNumberOfProjects(service, { configuredProjects: 1 + verifier.length }); - configFiles.forEach((configFile, index) => { - checkProjectActualFiles( - service.configuredProjects.get(configFile)!, - noDts ? - expectedProjectActualFiles[index].filter(f => f.toLowerCase() !== dtsPath) : - expectedProjectActualFiles[index] - ); - }); + if (onHostCreate) { + onHostCreate(host); } + const session = createSession(host); + const verifiers = verifier(withRefs); + openFilesForSession([...openFiles(verifiers), randomFile], session); + return { host, session, verifiers }; + } - function verifyInfos(session: TestSession, host: TestServerHost) { - verifyInfosWithRandom(session, host, openInfos, closedInfos, otherWatchedFiles); - } - - function verifyInfosWhenNoMapFile(session: TestSession, host: TestServerHost, dependencyTsOK?: boolean) { - const dtsMapClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsMapPath ? f : undefined); - verifyInfosWithRandom( - session, - host, - openInfos, - closedInfos.filter(f => f !== dtsMapClosedInfo && (dependencyTsOK || f !== dependencyTs.path)), - dtsMapClosedInfo ? otherWatchedFiles.concat(dtsMapClosedInfo) : otherWatchedFiles + function checkProject(session: TestSession, verifiers: readonly DocumentPositionMapperVerifier[], noDts?: true) { + const service = session.getProjectService(); + checkNumberOfProjects(service, { configuredProjects: 1 + verifiers.length }); + verifiers.forEach(({ configFile, expectedProjectActualFiles }) => { + checkProjectActualFiles( + service.configuredProjects.get(configFile.path.toLowerCase())!, + noDts ? + expectedProjectActualFiles.filter(f => f.toLowerCase() !== dtsPath) : + expectedProjectActualFiles ); - } + }); + } - function verifyInfosWhenNoDtsFile(session: TestSession, host: TestServerHost, watchDts: boolean, dependencyTsOk?: boolean, depedencyMapOk?: boolean) { - const dtsMapClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsMapPath ? f : undefined); - const dtsClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsPath ? f : undefined); - verifyInfosWithRandom( - session, - host, - openInfos, - closedInfos.filter(f => (depedencyMapOk || f !== dtsMapClosedInfo) && f !== dtsClosedInfo && (dependencyTsOk || f !== dependencyTs.path)), - dtsClosedInfo && watchDts ? - otherWatchedFiles.concat(dtsClosedInfo) : - otherWatchedFiles - ); + function firstAction(session: TestSession, verifiers: readonly DocumentPositionMapperVerifier[]) { + for (const { action } of getActionInfo(verifiers, "main")) { + const { request } = action(1); + session.executeCommandSeq(request); } + } - function verifyDocumentPositionMapper(session: TestSession, dependencyMap: server.ScriptInfo | undefined, documentPositionMapper: server.ScriptInfo["documentPositionMapper"], notEqual?: true) { - assert.strictEqual(session.getProjectService().filenameToScriptInfo.get(dtsMapPath), dependencyMap); - if (dependencyMap) { - if (notEqual) { - assert.notStrictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); - } - else { - assert.strictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); - } + function verifyAction(session: TestSession, { reqName, request, expectedResponse }: Action) { + const { response } = session.executeCommandSeq(request); + assert.deepEqual(response, expectedResponse, `Failed Request: ${reqName}`); + } + + function verifyScriptInfoPresence(session: TestSession, path: string, expectedToBePresent: boolean, reqName: string) { + const info = session.getProjectService().filenameToScriptInfo.get(path); + if (expectedToBePresent) { + assert.isDefined(info, `${reqName}:: ${path} expected to be present`); + } + else { + assert.isUndefined(info, `${reqName}:: ${path} expected to be not present`); + } + return info; + } + + function verifyDocumentPositionMapper(session: TestSession, dependencyMap: server.ScriptInfo | undefined, documentPositionMapper: server.ScriptInfo["documentPositionMapper"], equal: boolean) { + assert.strictEqual(session.getProjectService().filenameToScriptInfo.get(dtsMapPath), dependencyMap); + if (dependencyMap) { + if (equal) { + assert.strictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); + } + else { + assert.notStrictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); } } + } - function action(verifier: DocumentPositionMapperVerifier, fn: number, session: TestSession, useDependencyChange?: boolean) { - const { reqName, request, expectedResponse, expectedResponseNoMap, expectedResponseNoDts, requestDependencyChange, expectedResponseDependencyChange } = verifier.actionGetter(fn); - const { response } = session.executeCommandSeq(useDependencyChange ? requestDependencyChange || request : request); - return { reqName, response, expectedResponse, expectedResponseNoMap, expectedResponseNoDts, expectedResponseDependencyChange, verifier }; - } - - function firstAction(session: TestSession) { - verifier.forEach(v => action(v, 1, session)); - } - - function verifyAllFnActionWorker(session: TestSession, verifyAction: (result: ReturnType, dtsInfo: server.ScriptInfo | undefined, isFirst: boolean) => void, dtsAbsent?: boolean, useDependencyChange?: boolean) { - // action - let isFirst = true; - for (const v of verifier) { - for (let fn = 1; fn <= 5; fn++) { - const result = action(v, fn, session, useDependencyChange); - const dtsInfo = session.getProjectService().filenameToScriptInfo.get(dtsPath); - if (dtsAbsent) { - assert.isUndefined(dtsInfo); - } - else { - assert.isDefined(dtsInfo); - } - verifyAction(result, dtsInfo, isFirst); - isFirst = false; - } + function getActionInfo(verifiers: readonly DocumentPositionMapperVerifier[], actionKey: ActionKey): ActionInfo[] { + return verifiers.map(v => { + let actionInfoGetter = v[actionKey]; + while (isString(actionInfoGetter)) { + actionInfoGetter = v[actionInfoGetter]; } - } + return actionInfoGetter(); + }); + } - function dtsAbsent() { - return withRefs && !contains(closedInfos, dtsPath, (a, b) => a.toLowerCase() === b.toLowerCase()); - } - - function verifyAllFnAction( - session: TestSession, - host: TestServerHost, - firstDocumentPositionMapperNotEquals?: true, - dependencyMap?: server.ScriptInfo, - documentPositionMapper?: server.ScriptInfo["documentPositionMapper"], - useDependencyChange?: boolean - ) { - // action - verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse, expectedResponseDependencyChange }, dtsInfo, isFirst) => { - assert.deepEqual(response, useDependencyChange ? expectedResponseDependencyChange || expectedResponse : expectedResponse, `Failed on ${reqName}`); - verifyInfos(session, host); - if (dtsInfo) assert.equal(dtsInfo.sourceMapFilePath, dtsMapPath); - if (isFirst) { - if (dependencyMap) { - verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, firstDocumentPositionMapperNotEquals); - documentPositionMapper = dependencyMap.documentPositionMapper; - } - else { - dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath); - documentPositionMapper = dependencyMap && dependencyMap.documentPositionMapper; - } - } - else { - verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper); - } - }, dtsAbsent(), useDependencyChange); - return { dependencyMap, documentPositionMapper }; - } - - function verifyAllFnActionWithNoMap( - session: TestSession, - host: TestServerHost, - dependencyTsOK?: true - ) { - let sourceMapFilePath: server.ScriptInfo["sourceMapFilePath"]; - // action - verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse, expectedResponseNoMap }, dtsInfo, isFirst) => { - assert.deepEqual(response, withRefs ? expectedResponse : expectedResponseNoMap || expectedResponse, `Failed on ${reqName}`); - verifyInfosWhenNoMapFile(session, host, dependencyTsOK); - assert.isUndefined(session.getProjectService().filenameToScriptInfo.get(dtsMapPath)); - if (!withRefs) { - if (isFirst) { - assert.isNotString(dtsInfo!.sourceMapFilePath); - assert.isNotFalse(dtsInfo!.sourceMapFilePath); - assert.isDefined(dtsInfo!.sourceMapFilePath); - sourceMapFilePath = dtsInfo!.sourceMapFilePath; - } - else { - assert.equal(dtsInfo!.sourceMapFilePath, sourceMapFilePath); - } - } - }, dtsAbsent()); - return sourceMapFilePath; - } - - function verifyAllFnActionWithNoDts( - session: TestSession, - host: TestServerHost, - dependencyTsAndMapOk?: true - ) { - // action - verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse, expectedResponseNoDts, verifier }) => { - assert.deepEqual(response, withRefs ? expectedResponse : expectedResponseNoDts || expectedResponse, `Failed on ${reqName}`); - verifyInfosWhenNoDtsFile( + interface VerifyAllFnAction { + session: TestSession; + host: TestServerHost; + verifiers: readonly DocumentPositionMapperVerifier[]; + actionKey: ActionKey; + sourceMapPath?: server.ScriptInfo["sourceMapFilePath"]; + dependencyMap?: server.ScriptInfo | undefined; + documentPositionMapper?: server.ScriptInfo["documentPositionMapper"]; + firstEquals?: boolean; + } + interface VerifyAllFnActionResult { + actionInfos: readonly ActionInfo[]; + actionKey: ActionKey; + dependencyMap: server.ScriptInfo | undefined; + documentPositionMapper: server.ScriptInfo["documentPositionMapper"] | undefined; + } + function verifyAllFnAction({ + session, + host, + verifiers, + actionKey, + dependencyMap, + documentPositionMapper, + firstEquals + }: VerifyAllFnAction): VerifyAllFnActionResult { + const actionInfos = getActionInfo(verifiers, actionKey); + let sourceMapPath: server.ScriptInfo["sourceMapFilePath"] | undefined; + // action + let first = true; + for (const { action, closedInfos, otherWatchedFiles, expectsDts, expectsMap } of actionInfos) { + for (let fn = 1; fn <= 5; fn++) { + const fnAction = action(fn); + verifyAction(session, fnAction); + const dtsInfo = verifyScriptInfoPresence(session, dtsPath, expectsDts, fnAction.reqName); + const dtsMapInfo = verifyScriptInfoPresence(session, dtsMapPath, expectsMap, fnAction.reqName); + verifyInfosWithRandom( session, host, - // Even when project actual file contains dts, its not watched because the dts is in another folder and module resolution just fails - // instead of succeeding to source file and then mapping using project reference (When using usage location) - // But watched if sourcemapper is in source project since we need to keep track of dts to update the source mapper for any potential usages - verifier.expectedProjectActualFiles.every(f => f.toLowerCase() !== dtsPath), - /*dependencyTsOk*/ withRefs || dependencyTsAndMapOk, - /*dependencyMapOk*/ dependencyTsAndMapOk + openFiles(verifiers).map(f => f.path), + closedInfos(), + otherWatchedFiles(), + `${actionKey}:: ${fnAction.reqName}` ); - }, /*dtsAbsent*/ true); + + if (dtsInfo) { + if (first) { + if (dtsMapInfo) { + assert.equal(dtsInfo.sourceMapFilePath, dtsMapPath, `${actionKey}:: ${fnAction.reqName}`); + } + else { + assert.isNotString(dtsInfo.sourceMapFilePath); + assert.isNotFalse(dtsInfo.sourceMapFilePath); + assert.isDefined(dtsInfo.sourceMapFilePath); + } + } + else { + assert.equal(dtsInfo.sourceMapFilePath, sourceMapPath, `${actionKey}:: ${fnAction.reqName}`); + } + } + if (!first || firstEquals !== undefined) { + verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, !first || !!firstEquals); + } + sourceMapPath = dtsInfo && dtsInfo.sourceMapFilePath; + dependencyMap = dtsMapInfo; + documentPositionMapper = dependencyMap && dependencyMap.documentPositionMapper; + first = false; + } } - function verifyScenarioWithChangesWorker( - change: (host: TestServerHost, session: TestSession) => void, - afterActionDocumentPositionMapperNotEquals: true | undefined, - timeoutBeforeAction: boolean, - useDependencyChange?: boolean - ) { - const { host, session } = openTsFile(); + return { actionInfos, actionKey, dependencyMap, documentPositionMapper }; + } + + function verifyScriptInfoCollection( + session: TestSession, + host: TestServerHost, + verifiers: readonly DocumentPositionMapperVerifier[], + { dependencyMap, documentPositionMapper, actionInfos, actionKey }: VerifyAllFnActionResult + ) { + // Collecting at this point retains dependency.d.ts and map + closeFilesForSession([randomFile], session); + openFilesForSession([randomFile], session); + + const { closedInfos, otherWatchedFiles } = last(actionInfos); + verifyInfosWithRandom( + session, + host, + openFiles(verifiers).map(f => f.path), + closedInfos(), + otherWatchedFiles(), + `${actionKey} Collection` + ); + verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, /*equal*/ true); + + // Closing open file, removes dependencies too + closeFilesForSession([...openFiles(verifiers), randomFile], session); + openFilesForSession([randomFile], session); + verifyOnlyRandomInfos(session, host); + } + + function verifyScenarioAndScriptInfoCollection( + session: TestSession, + host: TestServerHost, + verifiers: readonly DocumentPositionMapperVerifier[], + actionKey: ActionKey, + noDts?: true + ) { + // Main scenario action + const result = verifyAllFnAction({ session, host, verifiers, actionKey }); + checkProject(session, verifiers, noDts); + verifyScriptInfoCollection(session, host, verifiers, result); + } + + function verifyScenarioWithChangesWorker( + { + scenarioName, + verifier, + withRefs, + change, + afterActionDocumentPositionMapperNotEquals, + afterChangeActionKey + }: VerifyScenarioWithChanges, + timeoutBeforeAction: boolean, + ) { + it(scenarioName, () => { + const { host, session, verifiers } = openTsFile({ verifier, withRefs }); // Create DocumentPositionMapper - firstAction(session); + firstAction(session, verifiers); const dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath); const documentPositionMapper = dependencyMap && dependencyMap.documentPositionMapper; // change - change(host, session); + change(host, session, verifiers); if (timeoutBeforeAction) { host.runQueuedTimeoutCallbacks(); - checkProject(session); - verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper); + checkProject(session, verifiers); + verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, /*equal*/ true); } // action - verifyAllFnAction(session, host, afterActionDocumentPositionMapperNotEquals, dependencyMap, documentPositionMapper, useDependencyChange); - } - - function verifyScenarioWithChanges( - scenarioName: string, - change: (host: TestServerHost, session: TestSession) => void, - afterActionDocumentPositionMapperNotEquals?: true, - useDependencyChange?: boolean - ) { - describe(scenarioName, () => { - it("when timeout occurs before request", () => { - verifyScenarioWithChangesWorker(change, afterActionDocumentPositionMapperNotEquals, /*timeoutBeforeAction*/ true, useDependencyChange); - }); - - it("when timeout does not occur before request", () => { - verifyScenarioWithChangesWorker(change, afterActionDocumentPositionMapperNotEquals, /*timeoutBeforeAction*/ false, useDependencyChange); - }); - }); - } - - function verifyMainScenarioAndScriptInfoCollection(session: TestSession, host: TestServerHost) { - // Main scenario action - const { dependencyMap, documentPositionMapper } = verifyAllFnAction(session, host); - checkProject(session); - verifyInfos(session, host); - - // Collecting at this point retains dependency.d.ts and map - closeFilesForSession([randomFile], session); - openFilesForSession([randomFile], session); - verifyInfos(session, host); - verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper); - - // Closing open file, removes dependencies too - closeFilesForSession([...openFiles, randomFile], session); - openFilesForSession([randomFile], session); - verifyOnlyRandomInfos(session, host); - } - - function verifyMainScenarioAndScriptInfoCollectionWithNoMap(session: TestSession, host: TestServerHost, dependencyTsOKInScenario?: true) { - // Main scenario action - verifyAllFnActionWithNoMap(session, host, withRefs || dependencyTsOKInScenario); - - // Collecting at this point retains dependency.d.ts and map watcher - closeFilesForSession([randomFile], session); - openFilesForSession([randomFile], session); - verifyInfosWhenNoMapFile(session, host, withRefs); - - // Closing open file, removes dependencies too - closeFilesForSession([...openFiles, randomFile], session); - openFilesForSession([randomFile], session); - verifyOnlyRandomInfos(session, host); - } - - function verifyMainScenarioAndScriptInfoCollectionWithNoDts(session: TestSession, host: TestServerHost, dependencyTsAndMapOk?: true) { - // Main scenario action - verifyAllFnActionWithNoDts(session, host, dependencyTsAndMapOk); - - // Collecting at this point retains dependency.d.ts and map watcher - closeFilesForSession([randomFile], session); - openFilesForSession([randomFile], session); - verifyInfosWhenNoDtsFile( + verifyAllFnAction({ session, host, - !!forEach(verifier, v => v.expectedProjectActualFiles.every(f => f.toLowerCase() !== dtsPath)), - /*dependencyTsOk*/ withRefs - ); + verifiers, + actionKey: afterChangeActionKey, + dependencyMap, + documentPositionMapper, + firstEquals: !afterActionDocumentPositionMapperNotEquals + }); + }); + } - // Closing open file, removes dependencies too - closeFilesForSession([...openFiles, randomFile], session); - openFilesForSession([randomFile], session); - verifyOnlyRandomInfos(session, host); - } + interface VerifyScenarioWithChanges extends VerifierAndWithRefs { + scenarioName: string; + change: (host: TestServerHost, session: TestSession, verifiers: readonly DocumentPositionMapperVerifier[]) => void; + afterActionDocumentPositionMapperNotEquals?: true; + afterChangeActionKey: ActionKey; + } + function verifyScenarioWithChanges(verify: VerifyScenarioWithChanges) { + describe("when timeout occurs before request", () => { + verifyScenarioWithChangesWorker(verify, /*timeoutBeforeAction*/ true); + }); - function verifyScenarioWhenFileNotPresent( - scenarioName: string, - fileLocation: string, - verifyScenarioAndScriptInfoCollection: (session: TestSession, host: TestServerHost, dependencyTsOk?: true) => void, - noDts?: true - ) { - describe(scenarioName, () => { - it(mainScenario, () => { - const { host, session } = openTsFile(host => host.deleteFile(fileLocation)); - checkProject(session, noDts); + describe("when timeout does not occur before request", () => { + verifyScenarioWithChangesWorker(verify, /*timeoutBeforeAction*/ false); + }); + } - verifyScenarioAndScriptInfoCollection(session, host); + interface VerifyScenarioWhenFileNotPresent extends VerifierAndWithRefs { + scenarioName: string; + fileLocation: string; + fileNotPresentKey: ActionKey; + fileCreatedKey: ActionKey; + fileDeletedKey: ActionKey; + noDts?: true; + } + function verifyScenarioWhenFileNotPresent({ + scenarioName, + verifier, + withRefs, + fileLocation, + fileNotPresentKey, + fileCreatedKey, + fileDeletedKey, + noDts + }: VerifyScenarioWhenFileNotPresent) { + describe(scenarioName, () => { + it("when file is not present", () => { + const { host, session, verifiers } = openTsFile({ + verifier, + withRefs, + onHostCreate: host => host.deleteFile(fileLocation) }); + checkProject(session, verifiers, noDts); - it("when file is created", () => { - let fileContents: string | undefined; - const { host, session } = openTsFile(host => { + verifyScenarioAndScriptInfoCollection(session, host, verifiers, fileNotPresentKey, noDts); + }); + + it("when file is created after actions on projects", () => { + let fileContents: string | undefined; + const { host, session, verifiers } = openTsFile({ + verifier, + withRefs, + onHostCreate: host => { fileContents = host.readFile(fileLocation); host.deleteFile(fileLocation); - }); - firstAction(session); - - host.writeFile(fileLocation, fileContents!); - verifyMainScenarioAndScriptInfoCollection(session, host); + } }); + firstAction(session, verifiers); - it("when file is deleted", () => { - const { host, session } = openTsFile(); - firstAction(session); - - // The dependency file is deleted when orphan files are collected - host.deleteFile(fileLocation); - verifyScenarioAndScriptInfoCollection(session, host, /*dependencyTsOk*/ true); - }); + host.writeFile(fileLocation, fileContents!); + verifyScenarioAndScriptInfoCollection(session, host, verifiers, fileCreatedKey); }); - } + it("when file is deleted after actions on the projects", () => { + const { host, session, verifiers } = openTsFile({ verifier, withRefs }); + firstAction(session, verifiers); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(fileLocation); + // Verify with deleted action key + const result = verifyAllFnAction({ session, host, verifiers, actionKey: fileDeletedKey }); + checkProject(session, verifiers, noDts); + + // Script info collection should behave as fileNotPresentKey + verifyScriptInfoCollection( + session, + host, + verifiers, + { + actionInfos: getActionInfo(verifiers, fileNotPresentKey), + actionKey: result.actionKey, + dependencyMap: undefined, + documentPositionMapper: undefined + } + ); + }); + }); + } + + function verifyScenarioWorker({ mainScenario, verifier }: VerifyScenario, withRefs: boolean) { it(mainScenario, () => { - const { host, session } = openTsFile(); - checkProject(session); - - verifyMainScenarioAndScriptInfoCollection(session, host); + const { host, session, verifiers } = openTsFile({ withRefs, verifier }); + checkProject(session, verifiers); + verifyScenarioAndScriptInfoCollection(session, host, verifiers, "main"); }); // Edit - verifyScenarioWithChanges( - "when usage file changes, document position mapper doesnt change", - (_host, session) => openFiles.forEach( - (openFile, index) => session.executeCommandSeq({ + verifyScenarioWithChanges({ + scenarioName: "when usage file changes, document position mapper doesnt change", + verifier, + withRefs, + change: (_host, session, verifiers) => verifiers.forEach( + verifier => session.executeCommandSeq({ command: protocol.CommandTypes.Change, - arguments: { file: openFile.path, line: openFileLastLines[index], offset: 1, endLine: openFileLastLines[index], endOffset: 1, insertString: "const x = 10;" } + arguments: { + file: verifier.openFile.path, + line: verifier.openFileLastLine, + offset: 1, + endLine: verifier.openFileLastLine, + endOffset: 1, + insertString: "const x = 10;" + } }) - ) - ); + ), + afterChangeActionKey: "main" + }); // Edit dts to add new fn - verifyScenarioWithChanges( - "when dependency .d.ts changes, document position mapper doesnt change", - host => host.writeFile( + verifyScenarioWithChanges({ + scenarioName: "when dependency .d.ts changes, document position mapper doesnt change", + verifier, + withRefs, + change: host => host.writeFile( dtsLocation, host.readFile(dtsLocation)!.replace( "//# sourceMappingURL=FnS.d.ts.map", `export declare function fn6(): void; //# sourceMappingURL=FnS.d.ts.map` ) - ) - ); + ), + afterChangeActionKey: "main" + }); // Edit map file to represent added new line - verifyScenarioWithChanges( - "when dependency file's map changes", - host => host.writeFile( + verifyScenarioWithChanges({ + scenarioName: "when dependency file's map changes", + verifier, + withRefs, + change: host => host.writeFile( dtsMapLocation, `{"version":3,"file":"FnS.d.ts","sourceRoot":"","sources":["../dependency/FnS.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,eAAO,MAAM,CAAC,KAAK,CAAC"}` ), - /*afterActionDocumentPositionMapperNotEquals*/ true - ); + afterChangeActionKey: "main", + afterActionDocumentPositionMapperNotEquals: true + }); - verifyScenarioWhenFileNotPresent( - "when map file is not present", - dtsMapLocation, - verifyMainScenarioAndScriptInfoCollectionWithNoMap - ); + verifyScenarioWhenFileNotPresent({ + scenarioName: "with depedency files map file", + verifier, + withRefs, + fileLocation: dtsMapLocation, + fileNotPresentKey: "noMap", + fileCreatedKey: "mapFileCreated", + fileDeletedKey: "mapFileDeleted" + }); - verifyScenarioWhenFileNotPresent( - "when .d.ts file is not present", - dtsLocation, - verifyMainScenarioAndScriptInfoCollectionWithNoDts, - /*noDts*/ true - ); + verifyScenarioWhenFileNotPresent({ + scenarioName: "with depedency .d.ts file", + verifier, + withRefs, + fileLocation: dtsLocation, + fileNotPresentKey: "noDts", + fileCreatedKey: "dtsFileCreated", + fileDeletedKey: "dtsFileDeleted", + noDts: true + }); if (withRefs) { - verifyScenarioWithChanges( - "when defining project source changes", - (host, session) => { + verifyScenarioWithChanges({ + scenarioName: "when defining project source changes", + verifier, + withRefs, + change: (host, session, verifiers) => { // Make change, without rebuild of solution - if (contains(openInfos, dependencyTs.path)) { + if (contains(openFiles(verifiers), dependencyTs)) { session.executeCommandSeq({ command: protocol.CommandTypes.Change, arguments: { @@ -724,96 +821,340 @@ fn5(); ${dependencyTs.content}`); } }, - /*afterActionDocumentPositionMapperNotEquals*/ undefined, - /*useDepedencyChange*/ true - ); + afterChangeActionKey: "dependencyChange" + }); it("when d.ts file is not generated", () => { const host = createServerHost(files); const session = createSession(host); - openFilesForSession([...openFiles, randomFile], session); - - const expectedClosedInfos = closedInfos.filter(f => f.toLowerCase() !== dtsPath && f.toLowerCase() !== dtsMapPath); - // If closed infos includes dts and dtsMap, watch dts since its not present - const expectedWatchedFiles = closedInfos.length === expectedClosedInfos.length ? - otherWatchedFiles : - otherWatchedFiles.concat(dtsPath); - // Main scenario action - verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse }) => { - assert.deepEqual(response, expectedResponse, `Failed on ${reqName}`); - verifyInfosWithRandom(session, host, openInfos, expectedClosedInfos, expectedWatchedFiles); - verifyDocumentPositionMapper(session, /*dependencyMap*/ undefined, /*documentPositionMapper*/ undefined); - }, /*dtsAbsent*/ true); - checkProject(session); - - // Collecting at this point retains dependency.d.ts and map - closeFilesForSession([randomFile], session); - openFilesForSession([randomFile], session); - verifyInfosWithRandom(session, host, openInfos, expectedClosedInfos, expectedWatchedFiles); - verifyDocumentPositionMapper(session, /*dependencyMap*/ undefined, /*documentPositionMapper*/ undefined); - - // Closing open file, removes dependencies too - closeFilesForSession([...openFiles, randomFile], session); - openFilesForSession([randomFile], session); - verifyOnlyRandomInfos(session, host); + const verifiers = verifier(withRefs); + openFilesForSession([...openFiles(verifiers), randomFile], session); + verifyScenarioAndScriptInfoCollection(session, host, verifiers, "noBuild"); }); } } - function verifyScenarios(withRefs: boolean) { - describe(withRefs ? "when main tsconfig has project reference" : "when main tsconfig doesnt have project reference", () => { - const usageVerifier: DocumentPositionMapperVerifier = { - openFile: mainTs, - expectedProjectActualFiles: withRefs ? - [mainTs.path, libFile.path, mainConfig.path, dependencyTs.path] : - [mainTs.path, libFile.path, mainConfig.path, dtsPath], - actionGetter: gotoDefintinionFromMainTs, - openFileLastLine: 14 - }; - describe("from project that uses dependency", () => { - const closedInfos = withRefs ? - [dependencyTs.path, dependencyConfig.path, libFile.path] : - [dependencyTs.path, libFile.path, dtsPath, dtsMapLocation]; - verifyDocumentPositionMapperUpdates( - "can go to definition correctly", - [usageVerifier], - closedInfos, - withRefs - ); - }); - - const definingVerifier: DocumentPositionMapperVerifier = { - openFile: dependencyTs, - expectedProjectActualFiles: [dependencyTs.path, libFile.path, dependencyConfig.path], - actionGetter: renameFromDependencyTs, - openFileLastLine: 6, - }; - describe("from defining project", () => { - const closedInfos = [libFile.path, dtsLocation, dtsMapLocation]; - verifyDocumentPositionMapperUpdates( - "rename locations from dependency", - [definingVerifier], - closedInfos, - withRefs - ); - }); - - describe("when opening depedency and usage project", () => { - const closedInfos = withRefs ? - [libFile.path, dependencyConfig.path] : - [libFile.path, dtsPath, dtsMapLocation]; - verifyDocumentPositionMapperUpdates( - "goto Definition in usage and rename locations from defining project", - [usageVerifier, { ...definingVerifier, actionGetter: renameFromDependencyTsWithBothProjectsOpen }], - closedInfos, - withRefs - ); - }); + interface VerifyScenario { + mainScenario: string; + verifier: (withRefs: boolean) => readonly DocumentPositionMapperVerifier[]; + } + function verifyScenario(scenario: VerifyScenario) { + describe("when main tsconfig doesnt have project reference", () => { + verifyScenarioWorker(scenario, /*withRefs*/ false); + }); + describe("when main tsconfig has project reference", () => { + verifyScenarioWorker(scenario, /*withRefs*/ true); }); } - verifyScenarios(/*withRefs*/ false); - verifyScenarios(/*withRefs*/ true); + describe("from project that uses dependency", () => { + function goToDefActionInfo(withRefs: boolean): ActionInfo { + return { + action: goToDefFromMainTs, + closedInfos: () => withRefs ? + [dependencyTs.path, dependencyConfig.path, libFile.path] : + [dependencyTs.path, libFile.path, dtsPath, dtsMapLocation], + otherWatchedFiles: () => [mainConfig.path], + expectsDts: !withRefs, // Dts script info present only if no project reference + expectsMap: !withRefs // Map script info present only if no project reference + }; + } + + function goToDefNoMapActionInfo(withRefs: boolean): ActionInfo { + return { + ...goToDefActionInfo(withRefs), + action: withRefs ? + goToDefFromMainTs : + goToDefFromMainTsWithNoMap, + closedInfos: () => withRefs ? + [dependencyTs.path, dependencyConfig.path, libFile.path] : + [libFile.path, dtsPath], // Because map is deleted, dts and dependency are released + otherWatchedFiles: () => withRefs ? + [mainConfig.path] : + [mainConfig.path, dtsMapPath], // Watches deleted file + expectsMap: false + }; + } + + function goToDefNoDtsActionInfo(withRefs: boolean): ActionInfo { + return { + ...goToDefActionInfo(withRefs), + action: withRefs ? + goToDefFromMainTs : + goToDefFromMainTsWithNoDts, + closedInfos: () => withRefs ? + [dependencyTs.path, dependencyConfig.path, libFile.path] : + [libFile.path], // No dts means no map, no dependency + expectsDts: false, + expectsMap: false + }; + } + + verifyScenario({ + mainScenario: "can go to definition correctly", + verifier: withRefs => [ + { + ...goToDefFromMainTsProjectInfoVerifier(withRefs), + main: () => goToDefActionInfo(withRefs), + noMap: () => goToDefNoMapActionInfo(withRefs), + mapFileCreated: "main", + mapFileDeleted: () => ({ + ...goToDefNoMapActionInfo(withRefs), + closedInfos: () => withRefs ? + [dependencyTs.path, dependencyConfig.path, libFile.path] : + // The script info for depedency is collected only after file open + [dependencyTs.path, libFile.path, dtsPath] + }), + noDts: () => goToDefNoDtsActionInfo(withRefs), + dtsFileCreated: "main", + dtsFileDeleted: () => ({ + ...goToDefNoDtsActionInfo(withRefs), + // The script info for map is collected only after file open + closedInfos: () => withRefs ? + [dependencyTs.path, dependencyConfig.path, libFile.path] : + [dependencyTs.path, libFile.path, dtsMapLocation], + expectsMap: !withRefs + }), + dependencyChange: () => ({ + ...goToDefActionInfo(withRefs), + action: goToDefFromMainTsWithDependencyChange, + expectsDts: false, + expectsMap: false + }), + noBuild: "main" + } + ] + }); + }); + + describe("from defining project", () => { + function renameActionInfo(): ActionInfo { + return { + action: renameFromDependencyTs, + closedInfos: () => [libFile.path, dtsLocation, dtsMapLocation], + otherWatchedFiles: () => [dependencyConfig.path], + expectsDts: true, + expectsMap: true + }; + } + + function renameNoMapActionInfo(): ActionInfo { + return { + ...renameActionInfo(), + closedInfos: () => [libFile.path, dtsLocation], // No map + otherWatchedFiles: () => [dependencyConfig.path, dtsMapLocation], // watch map + expectsMap: false + }; + } + + function renameNoDtsActionInfo(): ActionInfo { + return { + action: renameFromDependencyTs, + closedInfos: () => [libFile.path], // no dts or map since dts itself doesnt exist + otherWatchedFiles: () => [dependencyConfig.path, dtsPath], // watch deleted file + expectsDts: false, + expectsMap: false + }; + } + + verifyScenario({ + mainScenario: "rename locations from dependency", + verifier: () => [ + { + ...renameFromDependencyTsProjectInfoVerifier(), + main: renameActionInfo, + noMap: renameNoMapActionInfo, + mapFileCreated: "main", + mapFileDeleted: "noMap", + noDts: renameNoDtsActionInfo, + dtsFileCreated: "main", + dtsFileDeleted: () => ({ + ...renameNoDtsActionInfo(), + // Map is collected after file open + closedInfos: () => [libFile.path, dtsMapLocation], + expectsMap: true + }), + dependencyChange: () => ({ + ...renameActionInfo(), + action: renameFromDependencyTsWithDependencyChange + }), + noBuild: () => ({ + action: renameFromDependencyTs, + closedInfos: () => [libFile.path], // No dts or map since its not built/present + // Watching for creation of dts so that it can give correct results across projects + otherWatchedFiles: () => [dependencyConfig.path, dtsPath], + expectsDts: false, + expectsMap: false + }) + } + ] + }); + }); + + describe("when opening depedency and usage project", () => { + function closedInfos(withRefs: boolean) { + // DependencyTs is open, so omit it from closed infos + return () => withRefs ? + [dependencyConfig.path, libFile.path] : + [libFile.path, dtsPath, dtsMapLocation]; + } + + function otherWatchedFiles(withRefs: boolean) { + return () => withRefs ? + [mainConfig.path] : // Its in closed info + [mainConfig.path, dependencyConfig.path]; + } + + function goToDefActionInfo(withRefs: boolean): ActionInfo { + return { + action: goToDefFromMainTs, + closedInfos: closedInfos(withRefs), + otherWatchedFiles: otherWatchedFiles(withRefs), + expectsDts: !withRefs, // Dts script info present only if no project reference + expectsMap: !withRefs // Map script info present only if no project reference + }; + } + + function renameActionInfo(withRefs: boolean): ActionInfo { + return { + action: renameFromDependencyTsWithBothProjectsOpen, + closedInfos: closedInfos(withRefs), + otherWatchedFiles: otherWatchedFiles(withRefs), + expectsDts: !withRefs, // Dts script info present only if no project reference + expectsMap: !withRefs // Map script info present only if no project reference + }; + } + + function closedInfosNoMap(withRefs: boolean) { + return withRefs ? + closedInfos(withRefs) : + () => [libFile.path, dtsPath]; // No map + } + + function otherWatchedFilesNoMap(withRefs: boolean) { + return withRefs ? + otherWatchedFiles(withRefs) : + () => [mainConfig.path, dependencyConfig.path, dtsMapLocation]; // Watch map file + } + + function goToDefNoMapActionInfo(withRefs: boolean): ActionInfo { + return { + ...goToDefActionInfo(withRefs), + action: withRefs ? + goToDefFromMainTs : + goToDefFromMainTsWithNoMap, + closedInfos: closedInfosNoMap(withRefs), + otherWatchedFiles: otherWatchedFilesNoMap(withRefs), + expectsMap: false + }; + } + + function renameNoMapActionInfo(withRefs: boolean): ActionInfo { + return { + ...renameActionInfo(withRefs), + action: withRefs ? + renameFromDependencyTsWithBothProjectsOpen : + renameFromDependencyTs, + closedInfos: closedInfosNoMap(withRefs), + otherWatchedFiles: otherWatchedFilesNoMap(withRefs), + expectsMap: false + }; + } + + function closedInfosNoDts(withRefs: boolean) { + return withRefs ? + closedInfos(withRefs) : + () => [libFile.path]; // No dts or map + } + + function otherWatchedFilesNoDts(withRefs: boolean) { + return withRefs ? + otherWatchedFiles(withRefs) : + () => [mainConfig.path, dependencyConfig.path, dtsPath]; // Watch dts + } + + function goToDefNoDtsActionInfo(withRefs: boolean): ActionInfo { + return { + ...goToDefActionInfo(withRefs), + action: withRefs ? + goToDefFromMainTs : + goToDefFromMainTsWithNoDts, + closedInfos: closedInfosNoDts(withRefs), + expectsDts: false, + expectsMap: false + }; + } + + function renameNoDtsActionInfo(withRefs: boolean): ActionInfo { + return { + action: withRefs ? + renameFromDependencyTsWithBothProjectsOpen : + renameFromDependencyTs, + closedInfos: closedInfosNoDts(withRefs), + otherWatchedFiles: otherWatchedFilesNoDts(withRefs), + expectsDts: false, + expectsMap: false + }; + } + + function closedInfosDtsFileDeleted(withRefs: boolean) { + // Map collection after file open + return withRefs ? + closedInfos(withRefs) : + () => [libFile.path, dtsMapLocation]; + } + + verifyScenario({ + mainScenario: "goto Definition in usage and rename locations from defining project", + verifier: withRefs => [ + { + ...goToDefFromMainTsProjectInfoVerifier(withRefs), + main: () => goToDefActionInfo(withRefs), + noMap: () => goToDefNoMapActionInfo(withRefs), + mapFileCreated: "main", + mapFileDeleted: "noMap", + noDts: () => goToDefNoDtsActionInfo(withRefs), + dtsFileCreated: "main", + dtsFileDeleted: () => ({ + ...goToDefNoDtsActionInfo(withRefs), + // Map collection after file open + closedInfos: closedInfosDtsFileDeleted(withRefs), + expectsMap: !withRefs + }), + dependencyChange: () => ({ + ...goToDefActionInfo(withRefs), + action: goToDefFromMainTsWithDependencyChange, + expectsDts: false, + expectsMap: false + }), + noBuild: "main" + }, + { + ...renameFromDependencyTsProjectInfoVerifier(), + main: () => renameActionInfo(withRefs), + noMap: () => renameNoMapActionInfo(withRefs), + mapFileCreated: "main", + mapFileDeleted: "noMap", + noDts: () => renameNoDtsActionInfo(withRefs), + dtsFileCreated: "main", + dtsFileDeleted: () => ({ + ...renameNoDtsActionInfo(withRefs), + // Map collection after file open + closedInfos: closedInfosDtsFileDeleted(withRefs), + expectsMap: !withRefs + }), + dependencyChange: () => ({ + ...renameActionInfo(withRefs), + action: renameFromDependencyTsWithBothProjectsOpenWithDependencyChange + }), + noBuild: () => ({ + ...renameActionInfo(withRefs), + expectDts: false + }) + } + ] + }); + }); }); it("reusing d.ts files from composite and non composite projects", () => { From 15b68a93966530f184d471234e3a0566e8c37307 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 1 Jul 2019 14:43:11 -0700 Subject: [PATCH 16/25] Skip typechecking of source of project reference redirect --- src/compiler/builder.ts | 2 +- src/compiler/checker.ts | 4 +- src/compiler/program.ts | 2 +- src/compiler/utilities.ts | 9 +- src/server/scriptInfo.ts | 6 +- src/server/session.ts | 4 +- src/testRunner/tsconfig.json | 1 + .../tsserver/projectReferenceErrors.ts | 236 +++++++++++++ .../unittests/tsserver/projectReferences.ts | 322 +++++++++++------- 9 files changed, 460 insertions(+), 126 deletions(-) create mode 100644 src/testRunner/unittests/tsserver/projectReferenceErrors.ts diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 704cdaf23f..bd35bac6c5 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -407,7 +407,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) ); } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c2e6b0d518..4004030212 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -338,7 +338,7 @@ namespace ts { return node && getTypeArgumentConstraint(node); }, getSuggestionDiagnostics: (file, ct) => { - if (skipTypeChecking(file, compilerOptions)) { + if (skipTypeChecking(file, compilerOptions, host)) { return emptyArray; } @@ -29657,7 +29657,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; } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 78e5f31487..2149e175ca 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1701,7 +1701,7 @@ namespace ts { function getSemanticDiagnosticsForFileNoCache(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] | undefined { return runWithCancellationToken(() => { - if (skipTypeChecking(sourceFile, options)) { + if (skipTypeChecking(sourceFile, options, program)) { return emptyArray; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 562d066b64..ab6c0bfd83 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -8573,11 +8573,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 // '/// ' 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 { diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index 68c448a7e7..a2e75e3dac 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -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]; } } diff --git a/src/server/session.ts b/src/server/session.ts index ff339b06a8..a5c1357480 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1658,10 +1658,10 @@ namespace ts.server { } } - private createCheckList(fileNames: string[], defaultProject?: Project): PendingErrorCheck[] { + private createCheckList(fileNames: string[]): PendingErrorCheck[] { return mapDefined(fileNames, uncheckedFileName => { const fileName = toNormalizedPath(uncheckedFileName); - const project = defaultProject || this.projectService.tryGetDefaultProjectForFile(fileName); + const project = this.projectService.tryGetDefaultProjectForFile(fileName); return project && { fileName, project }; }); } diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index b2e7e3e78b..9d67215ffd 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -137,6 +137,7 @@ "unittests/tsserver/occurences.ts", "unittests/tsserver/openFile.ts", "unittests/tsserver/projectErrors.ts", + "unittests/tsserver/projectReferenceErrors.ts", "unittests/tsserver/projectReferences.ts", "unittests/tsserver/projects.ts", "unittests/tsserver/refactors.ts", diff --git a/src/testRunner/unittests/tsserver/projectReferenceErrors.ts b/src/testRunner/unittests/tsserver/projectReferenceErrors.ts new file mode 100644 index 0000000000..8006b91603 --- /dev/null +++ b/src/testRunner/unittests/tsserver/projectReferenceErrors.ts @@ -0,0 +1,236 @@ +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`; + 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({ + references: [{ path: "../dependency" }] + }) + }; + + 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: ReadonlyArray; + 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({ openFiles, expectedGetErr }: VerifyScenario) { + it("verifies the errors in open file", () => { + const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); + const session = createSession(host, { canUseEvents: true, }); + openFilesForSession(openFiles(), session); + + session.clearMessages(); + const expectedSequenceId = session.getNextSeq(); + const expected = expectedGetErr(); + session.executeCommandSeq({ + command: protocol.CommandTypes.Geterr, + arguments: { + delay: 0, + files: expected.map(f => f.file.path) + } + }); + + checkAllErrors({ session, host, expected, expectedSequenceId }); + }); + } + + function verifyErrorsUsingGeterrForProject({ openFiles, expectedGetErrForProject }: VerifyScenario) { + it("verifies the errors in projects", () => { + const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); + const session = createSession(host, { canUseEvents: true, }); + openFilesForSession(openFiles(), session); + + session.clearMessages(); + for (const expected of expectedGetErrForProject()) { + const expectedSequenceId = session.getNextSeq(); + session.executeCommandSeq({ + command: protocol.CommandTypes.GeterrForProject, + arguments: { + delay: 0, + file: expected.project + } + }); + + checkAllErrors({ session, host, expected: expected.errors, expectedSequenceId }); + } + }); + } + + interface GetErrDiagnostics { + file: File; + syntax: protocol.Diagnostic[]; + semantic: protocol.Diagnostic[]; + suggestion: protocol.Diagnostic[]; + } + interface GetErrForProjectDiagnostics { + project: string; + errors: ReadonlyArray; + } + interface VerifyScenario { + openFiles: () => ReadonlyArray; + expectedGetErr: () => ReadonlyArray; + expectedGetErrForProject: () => ReadonlyArray; + } + function verifyScenario(scenario: VerifyScenario) { + verifyErrorsUsingGeterr(scenario); + verifyErrorsUsingGeterrForProject(scenario); + } + + function emptyDiagnostics(file: File): GetErrDiagnostics { + return { + file, + syntax: emptyArray, + semantic: emptyArray, + suggestion: emptyArray + }; + } + + 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 + }; + } + + function usageProjectDiagnostics(): GetErrForProjectDiagnostics { + return { + project: usageTs.path, + errors: [ + usageDiagnostics(), + emptyDiagnostics(dependencyTs) + ] + }; + } + + function dependencyProjectDiagnostics(): GetErrForProjectDiagnostics { + return { + project: dependencyTs.path, + errors: [ + dependencyDiagnostics() + ] + }; + } + + describe("when dependency project is not open", () => { + verifyScenario({ + openFiles: () => [usageTs], + expectedGetErr: () => [ + usageDiagnostics() + ], + expectedGetErrForProject: () => [ + usageProjectDiagnostics(), + { + project: dependencyTs.path, + errors: [ + emptyDiagnostics(dependencyTs), + usageDiagnostics() + ] + } + ] + }); + }); + + describe("when the depedency file is open", () => { + verifyScenario({ + openFiles: () => [usageTs, dependencyTs], + expectedGetErr: () => [ + usageDiagnostics(), + dependencyDiagnostics(), + ], + expectedGetErrForProject: () => [ + usageProjectDiagnostics(), + dependencyProjectDiagnostics() + ] + }); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index c5712df2e1..f39ec72e03 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -373,6 +373,9 @@ fn5(); otherWatchedFiles: () => readonly string[]; expectsDts: boolean; expectsMap: boolean; + freshMapInfo?: boolean; + freshDocumentMapper?: boolean; + skipDtsMapCheck?: boolean; } type ActionKey = keyof ActionInfoVerifier; type ActionInfoGetterFn = () => ActionInfo; @@ -385,6 +388,9 @@ fn5(); } interface ActionInfoVerifier { main: ActionInfoGetter; + change: ActionInfoGetter; + dtsChange: ActionInfoGetter; + mapChange: ActionInfoGetter; noMap: ActionInfoGetter; mapFileCreated: ActionInfoGetter; mapFileDeleted: ActionInfoGetter; @@ -461,14 +467,21 @@ fn5(); return info; } - function verifyDocumentPositionMapper(session: TestSession, dependencyMap: server.ScriptInfo | undefined, documentPositionMapper: server.ScriptInfo["documentPositionMapper"], equal: boolean) { - assert.strictEqual(session.getProjectService().filenameToScriptInfo.get(dtsMapPath), dependencyMap); + interface VerifyDocumentPositionMapper { + session: TestSession; + dependencyMap: server.ScriptInfo | undefined; + documentPositionMapper: server.ScriptInfo["documentPositionMapper"]; + equal: boolean; + debugInfo: string; + } + function verifyDocumentPositionMapper({ session, dependencyMap, documentPositionMapper, equal, debugInfo }: VerifyDocumentPositionMapper) { + assert.strictEqual(session.getProjectService().filenameToScriptInfo.get(dtsMapPath), dependencyMap, debugInfo); if (dependencyMap) { if (equal) { - assert.strictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); + assert.strictEqual(dependencyMap.documentPositionMapper, documentPositionMapper, debugInfo); } else { - assert.notStrictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); + assert.notStrictEqual(dependencyMap.documentPositionMapper, documentPositionMapper, debugInfo); } } } @@ -491,7 +504,6 @@ fn5(); sourceMapPath?: server.ScriptInfo["sourceMapFilePath"]; dependencyMap?: server.ScriptInfo | undefined; documentPositionMapper?: server.ScriptInfo["documentPositionMapper"]; - firstEquals?: boolean; } interface VerifyAllFnActionResult { actionInfos: readonly ActionInfo[]; @@ -506,44 +518,62 @@ fn5(); actionKey, dependencyMap, documentPositionMapper, - firstEquals }: VerifyAllFnAction): VerifyAllFnActionResult { const actionInfos = getActionInfo(verifiers, actionKey); let sourceMapPath: server.ScriptInfo["sourceMapFilePath"] | undefined; // action let first = true; - for (const { action, closedInfos, otherWatchedFiles, expectsDts, expectsMap } of actionInfos) { + for (const { + action, + closedInfos, + otherWatchedFiles, + expectsDts, + expectsMap, + freshMapInfo, + freshDocumentMapper, + skipDtsMapCheck + } of actionInfos) { for (let fn = 1; fn <= 5; fn++) { const fnAction = action(fn); verifyAction(session, fnAction); - const dtsInfo = verifyScriptInfoPresence(session, dtsPath, expectsDts, fnAction.reqName); - const dtsMapInfo = verifyScriptInfoPresence(session, dtsMapPath, expectsMap, fnAction.reqName); + const debugInfo = `${actionKey}:: ${fnAction.reqName}:: ${fn}`; + const dtsInfo = verifyScriptInfoPresence(session, dtsPath, expectsDts, debugInfo); + const dtsMapInfo = verifyScriptInfoPresence(session, dtsMapPath, expectsMap, debugInfo); verifyInfosWithRandom( session, host, openFiles(verifiers).map(f => f.path), closedInfos(), otherWatchedFiles(), - `${actionKey}:: ${fnAction.reqName}` + debugInfo ); if (dtsInfo) { - if (first) { - if (dtsMapInfo) { - assert.equal(dtsInfo.sourceMapFilePath, dtsMapPath, `${actionKey}:: ${fnAction.reqName}`); - } - else { - assert.isNotString(dtsInfo.sourceMapFilePath); - assert.isNotFalse(dtsInfo.sourceMapFilePath); - assert.isDefined(dtsInfo.sourceMapFilePath); + if (first || (fn === 1 && freshMapInfo)) { + if (!skipDtsMapCheck) { + if (dtsMapInfo) { + assert.equal(dtsInfo.sourceMapFilePath, dtsMapPath, debugInfo); + } + else { + assert.isNotString(dtsInfo.sourceMapFilePath, debugInfo); + assert.isNotFalse(dtsInfo.sourceMapFilePath, debugInfo); + assert.isDefined(dtsInfo.sourceMapFilePath, debugInfo); + } } } else { - assert.equal(dtsInfo.sourceMapFilePath, sourceMapPath, `${actionKey}:: ${fnAction.reqName}`); + assert.equal(dtsInfo.sourceMapFilePath, sourceMapPath, debugInfo); } } - if (!first || firstEquals !== undefined) { - verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, !first || !!firstEquals); + + if (!first && (fn !== 1 || !freshMapInfo)) { + verifyDocumentPositionMapper({ + session, + dependencyMap, + documentPositionMapper, + equal: fn !== 1 || !freshDocumentMapper, + debugInfo + }); } sourceMapPath = dtsInfo && dtsInfo.sourceMapFilePath; dependencyMap = dtsMapInfo; @@ -566,15 +596,22 @@ fn5(); openFilesForSession([randomFile], session); const { closedInfos, otherWatchedFiles } = last(actionInfos); + const debugInfo = `${actionKey} Collection`; verifyInfosWithRandom( session, host, openFiles(verifiers).map(f => f.path), closedInfos(), otherWatchedFiles(), - `${actionKey} Collection` + debugInfo ); - verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, /*equal*/ true); + verifyDocumentPositionMapper({ + session, + dependencyMap, + documentPositionMapper, + equal: true, + debugInfo + }); // Closing open file, removes dependencies too closeFilesForSession([...openFiles(verifiers), randomFile], session); @@ -601,7 +638,6 @@ fn5(); verifier, withRefs, change, - afterActionDocumentPositionMapperNotEquals, afterChangeActionKey }: VerifyScenarioWithChanges, timeoutBeforeAction: boolean, @@ -619,7 +655,13 @@ fn5(); if (timeoutBeforeAction) { host.runQueuedTimeoutCallbacks(); checkProject(session, verifiers); - verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, /*equal*/ true); + verifyDocumentPositionMapper({ + session, + dependencyMap, + documentPositionMapper, + equal: true, + debugInfo: "After change timeout" + }); } // action @@ -629,8 +671,7 @@ fn5(); verifiers, actionKey: afterChangeActionKey, dependencyMap, - documentPositionMapper, - firstEquals: !afterActionDocumentPositionMapperNotEquals + documentPositionMapper }); }); } @@ -638,7 +679,6 @@ fn5(); interface VerifyScenarioWithChanges extends VerifierAndWithRefs { scenarioName: string; change: (host: TestServerHost, session: TestSession, verifiers: readonly DocumentPositionMapperVerifier[]) => void; - afterActionDocumentPositionMapperNotEquals?: true; afterChangeActionKey: ActionKey; } function verifyScenarioWithChanges(verify: VerifyScenarioWithChanges) { @@ -704,7 +744,7 @@ fn5(); // The dependency file is deleted when orphan files are collected host.deleteFile(fileLocation); // Verify with deleted action key - const result = verifyAllFnAction({ session, host, verifiers, actionKey: fileDeletedKey }); + verifyAllFnAction({ session, host, verifiers, actionKey: fileDeletedKey }); checkProject(session, verifiers, noDts); // Script info collection should behave as fileNotPresentKey @@ -714,7 +754,7 @@ fn5(); verifiers, { actionInfos: getActionInfo(verifiers, fileNotPresentKey), - actionKey: result.actionKey, + actionKey: fileNotPresentKey, dependencyMap: undefined, documentPositionMapper: undefined } @@ -748,7 +788,7 @@ fn5(); } }) ), - afterChangeActionKey: "main" + afterChangeActionKey: "change" }); // Edit dts to add new fn @@ -764,7 +804,7 @@ fn5(); //# sourceMappingURL=FnS.d.ts.map` ) ), - afterChangeActionKey: "main" + afterChangeActionKey: "dtsChange" }); // Edit map file to represent added new line @@ -776,8 +816,7 @@ fn5(); dtsMapLocation, `{"version":3,"file":"FnS.d.ts","sourceRoot":"","sources":["../dependency/FnS.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,eAAO,MAAM,CAAC,KAAK,CAAC"}` ), - afterChangeActionKey: "main", - afterActionDocumentPositionMapperNotEquals: true + afterChangeActionKey: "mapChange" }); verifyScenarioWhenFileNotPresent({ @@ -824,7 +863,7 @@ ${dependencyTs.content}`); afterChangeActionKey: "dependencyChange" }); - it("when d.ts file is not generated", () => { + it("when projects are not built", () => { const host = createServerHost(files); const session = createSession(host); const verifiers = verifier(withRefs); @@ -896,6 +935,12 @@ ${dependencyTs.content}`); { ...goToDefFromMainTsProjectInfoVerifier(withRefs), main: () => goToDefActionInfo(withRefs), + change: "main", + dtsChange: "main", + mapChange: () => ({ + ...goToDefActionInfo(withRefs), + freshDocumentMapper: true + }), noMap: () => goToDefNoMapActionInfo(withRefs), mapFileCreated: "main", mapFileDeleted: () => ({ @@ -938,15 +983,6 @@ ${dependencyTs.content}`); }; } - function renameNoMapActionInfo(): ActionInfo { - return { - ...renameActionInfo(), - closedInfos: () => [libFile.path, dtsLocation], // No map - otherWatchedFiles: () => [dependencyConfig.path, dtsMapLocation], // watch map - expectsMap: false - }; - } - function renameNoDtsActionInfo(): ActionInfo { return { action: renameFromDependencyTs, @@ -963,7 +999,18 @@ ${dependencyTs.content}`); { ...renameFromDependencyTsProjectInfoVerifier(), main: renameActionInfo, - noMap: renameNoMapActionInfo, + change: "main", + dtsChange: "main", + mapChange: () => ({ + ...renameActionInfo(), + freshDocumentMapper: true + }), + noMap: () => ({ + ...renameActionInfo(), + closedInfos: () => [libFile.path, dtsLocation], // No map + otherWatchedFiles: () => [dependencyConfig.path, dtsMapLocation], // watch map + expectsMap: false + }), mapFileCreated: "main", mapFileDeleted: "noMap", noDts: renameNoDtsActionInfo, @@ -992,24 +1039,16 @@ ${dependencyTs.content}`); }); describe("when opening depedency and usage project", () => { - function closedInfos(withRefs: boolean) { - // DependencyTs is open, so omit it from closed infos - return () => withRefs ? - [dependencyConfig.path, libFile.path] : - [libFile.path, dtsPath, dtsMapLocation]; - } - - function otherWatchedFiles(withRefs: boolean) { - return () => withRefs ? - [mainConfig.path] : // Its in closed info - [mainConfig.path, dependencyConfig.path]; - } - function goToDefActionInfo(withRefs: boolean): ActionInfo { return { action: goToDefFromMainTs, - closedInfos: closedInfos(withRefs), - otherWatchedFiles: otherWatchedFiles(withRefs), + // DependencyTs is open, so omit it from closed infos + closedInfos: () => withRefs ? + [dependencyConfig.path, libFile.path] : + [libFile.path, dtsPath, dtsMapLocation], + otherWatchedFiles: () => withRefs ? + [mainConfig.path] : // Its in closed info + [mainConfig.path, dependencyConfig.path], expectsDts: !withRefs, // Dts script info present only if no project reference expectsMap: !withRefs // Map script info present only if no project reference }; @@ -1018,33 +1057,30 @@ ${dependencyTs.content}`); function renameActionInfo(withRefs: boolean): ActionInfo { return { action: renameFromDependencyTsWithBothProjectsOpen, - closedInfos: closedInfos(withRefs), - otherWatchedFiles: otherWatchedFiles(withRefs), - expectsDts: !withRefs, // Dts script info present only if no project reference - expectsMap: !withRefs // Map script info present only if no project reference + // DependencyTs is open, so omit it from closed infos + closedInfos: () => withRefs ? + [dependencyConfig.path, libFile.path, dtsLocation, dtsMapLocation] : + [libFile.path, dtsPath, dtsMapLocation], + otherWatchedFiles: () => withRefs ? + [mainConfig.path] : // Its in closed info + [mainConfig.path, dependencyConfig.path], + expectsDts: true, + expectsMap: true }; } - function closedInfosNoMap(withRefs: boolean) { - return withRefs ? - closedInfos(withRefs) : - () => [libFile.path, dtsPath]; // No map - } - - function otherWatchedFilesNoMap(withRefs: boolean) { - return withRefs ? - otherWatchedFiles(withRefs) : - () => [mainConfig.path, dependencyConfig.path, dtsMapLocation]; // Watch map file - } - function goToDefNoMapActionInfo(withRefs: boolean): ActionInfo { return { - ...goToDefActionInfo(withRefs), action: withRefs ? goToDefFromMainTs : goToDefFromMainTsWithNoMap, - closedInfos: closedInfosNoMap(withRefs), - otherWatchedFiles: otherWatchedFilesNoMap(withRefs), + closedInfos: () => withRefs ? + [dependencyConfig.path, libFile.path] : + [libFile.path, dtsPath], // No map + otherWatchedFiles: () => withRefs ? + [mainConfig.path] : // Its in closed info + [mainConfig.path, dependencyConfig.path, dtsMapLocation], // Watch map file + expectsDts: !withRefs, // Dts script info present only if no project reference expectsMap: false }; } @@ -1055,31 +1091,25 @@ ${dependencyTs.content}`); action: withRefs ? renameFromDependencyTsWithBothProjectsOpen : renameFromDependencyTs, - closedInfos: closedInfosNoMap(withRefs), - otherWatchedFiles: otherWatchedFilesNoMap(withRefs), + closedInfos: () => withRefs ? + [dependencyConfig.path, libFile.path, dtsLocation] : + [libFile.path, dtsPath], // No map + otherWatchedFiles: () => withRefs ? + [mainConfig.path, dtsMapLocation] : // Its in closed info + [mainConfig.path, dependencyConfig.path, dtsMapLocation], // Watch map file expectsMap: false }; } - function closedInfosNoDts(withRefs: boolean) { - return withRefs ? - closedInfos(withRefs) : - () => [libFile.path]; // No dts or map - } - - function otherWatchedFilesNoDts(withRefs: boolean) { - return withRefs ? - otherWatchedFiles(withRefs) : - () => [mainConfig.path, dependencyConfig.path, dtsPath]; // Watch dts - } - function goToDefNoDtsActionInfo(withRefs: boolean): ActionInfo { return { ...goToDefActionInfo(withRefs), action: withRefs ? goToDefFromMainTs : goToDefFromMainTsWithNoDts, - closedInfos: closedInfosNoDts(withRefs), + closedInfos: () => withRefs ? + [dependencyConfig.path, libFile.path] : + [libFile.path], // No dts or map, expectsDts: false, expectsMap: false }; @@ -1090,49 +1120,100 @@ ${dependencyTs.content}`); action: withRefs ? renameFromDependencyTsWithBothProjectsOpen : renameFromDependencyTs, - closedInfos: closedInfosNoDts(withRefs), - otherWatchedFiles: otherWatchedFilesNoDts(withRefs), + closedInfos: () => withRefs ? + [dependencyConfig.path, libFile.path] : + [libFile.path], // No dts or map, + otherWatchedFiles: () => withRefs ? + [mainConfig.path, dtsLocation] : + [mainConfig.path, dependencyConfig.path, dtsPath], // Watch dts, expectsDts: false, expectsMap: false }; } - function closedInfosDtsFileDeleted(withRefs: boolean) { - // Map collection after file open - return withRefs ? - closedInfos(withRefs) : - () => [libFile.path, dtsMapLocation]; - } - verifyScenario({ mainScenario: "goto Definition in usage and rename locations from defining project", verifier: withRefs => [ { ...goToDefFromMainTsProjectInfoVerifier(withRefs), main: () => goToDefActionInfo(withRefs), + change: () => ({ + // Because before this rename is done the closed info remains same as rename's main operation + ...goToDefActionInfo(withRefs), + closedInfos: () => withRefs ? + [dependencyConfig.path, libFile.path, dtsLocation, dtsMapLocation] : + [libFile.path, dtsPath, dtsMapLocation], + expectsDts: true, + expectsMap: true + }), + dtsChange: "change", + mapChange: "change", noMap: () => goToDefNoMapActionInfo(withRefs), - mapFileCreated: "main", - mapFileDeleted: "noMap", + mapFileCreated: () => ({ + // Because before this rename is done the closed info remains same as rename's main + ...goToDefActionInfo(withRefs), + closedInfos: () => withRefs ? + [dependencyConfig.path, libFile.path, dtsLocation] : + [libFile.path, dtsPath, dtsMapLocation], + expectsDts: true, + // This operation doesnt need map so the map info path in dts is not refreshed + skipDtsMapCheck: withRefs + }), + mapFileDeleted: () => ({ + // Because before this rename is done the closed info remains same as rename's noMap operation + ...goToDefNoMapActionInfo(withRefs), + closedInfos: () => withRefs ? + [dependencyConfig.path, libFile.path, dtsLocation] : + [libFile.path, dtsPath], // No map, + expectsDts: true, + // This operation doesnt need map so the map info path in dts is not refreshed + skipDtsMapCheck: withRefs + }), noDts: () => goToDefNoDtsActionInfo(withRefs), - dtsFileCreated: "main", + dtsFileCreated: () => ({ + ...goToDefActionInfo(withRefs), + // Since the project for dependency is not updated, the watcher from rename for dts still there + otherWatchedFiles: () => withRefs ? + [mainConfig.path, dtsLocation] : + [mainConfig.path, dependencyConfig.path], + }), dtsFileDeleted: () => ({ ...goToDefNoDtsActionInfo(withRefs), // Map collection after file open - closedInfos: closedInfosDtsFileDeleted(withRefs), - expectsMap: !withRefs + closedInfos: () => withRefs ? + [dependencyConfig.path, libFile.path, dtsMapLocation] : + [libFile.path, dtsMapLocation], + expectsMap: true }), dependencyChange: () => ({ ...goToDefActionInfo(withRefs), action: goToDefFromMainTsWithDependencyChange, - expectsDts: false, - expectsMap: false + // From rename main action + closedInfos: () => withRefs ? + [dependencyConfig.path, libFile.path, dtsLocation, dtsMapLocation] : + [libFile.path, dtsPath, dtsMapLocation], + expectsDts: withRefs, + expectsMap: withRefs }), noBuild: "main" }, { ...renameFromDependencyTsProjectInfoVerifier(), - main: () => renameActionInfo(withRefs), - noMap: () => renameNoMapActionInfo(withRefs), + main: () => ({ + ...renameActionInfo(withRefs), + freshMapInfo: withRefs + }), + change: () => renameActionInfo(withRefs), + dtsChange: "change", + mapChange: () => ({ + ...renameActionInfo(withRefs), + freshDocumentMapper: withRefs + }), + noMap: () => ({ + ...renameNoMapActionInfo(withRefs), + freshMapInfo: withRefs, + freshDocumentMapper: withRefs + }), mapFileCreated: "main", mapFileDeleted: "noMap", noDts: () => renameNoDtsActionInfo(withRefs), @@ -1140,16 +1221,25 @@ ${dependencyTs.content}`); dtsFileDeleted: () => ({ ...renameNoDtsActionInfo(withRefs), // Map collection after file open - closedInfos: closedInfosDtsFileDeleted(withRefs), - expectsMap: !withRefs + closedInfos: () => withRefs ? + [dependencyConfig.path, libFile.path, dtsMapLocation] : + [libFile.path, dtsMapLocation], + expectsMap: true }), dependencyChange: () => ({ ...renameActionInfo(withRefs), action: renameFromDependencyTsWithBothProjectsOpenWithDependencyChange }), noBuild: () => ({ - ...renameActionInfo(withRefs), - expectDts: false + action: renameFromDependencyTsWithBothProjectsOpen, + closedInfos: () => withRefs ? + [dependencyConfig.path, libFile.path] : + [libFile.path], + otherWatchedFiles: () => withRefs ? + [mainConfig.path, dtsLocation] : // Its in closed info + [mainConfig.path, dependencyConfig.path], + expectsDts: false, + expectsMap: false }) } ] From 9be475bdaa9e40787e610ffe0a27ecfe9ae323a8 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 3 Jul 2019 15:47:32 -0700 Subject: [PATCH 17/25] Refactoring --- .../unittests/tsserver/projectReferences.ts | 532 ++++++++---------- 1 file changed, 222 insertions(+), 310 deletions(-) diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index f39ec72e03..f9445dc6d1 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -105,6 +105,7 @@ export function fn4() { } export function fn5() { } ` }; + const dependencyTsPath = dependencyTs.path.toLowerCase(); const dependencyConfig: File = { path: `${dependecyLocation}/tsconfig.json`, content: JSON.stringify({ compilerOptions: { composite: true, declarationMap: true, declarationDir: "../decls" } }) @@ -362,6 +363,13 @@ fn5(); }; } + function removePath(array: readonly string[], ...delPaths: string[]) { + return array.filter(a => { + const aLower = a.toLowerCase(); + return delPaths.every(dPath => dPath !== aLower); + }); + } + interface Action { reqName: string; request: Partial; @@ -369,8 +377,8 @@ fn5(); } interface ActionInfo { action: (fn: number) => Action; - closedInfos: () => readonly string[]; - otherWatchedFiles: () => readonly string[]; + closedInfos: readonly string[]; + otherWatchedFiles: readonly string[]; expectsDts: boolean; expectsMap: boolean; freshMapInfo?: boolean; @@ -379,7 +387,11 @@ fn5(); } type ActionKey = keyof ActionInfoVerifier; type ActionInfoGetterFn = () => ActionInfo; - type ActionInfoGetter = ActionInfoGetterFn | ActionKey; + type ActionInfoSpreader = [ + ActionKey, // Key to get initial value and pass this value to spread function + (actionInfo: ActionInfo) => Partial> + ]; + type ActionInfoGetter = ActionInfoGetterFn | ActionKey | ActionInfoSpreader; interface ProjectInfoVerifier { openFile: File; openFileLastLine: number; @@ -486,14 +498,25 @@ fn5(); } } + function getActionInfoOfVerfier(verifier: DocumentPositionMapperVerifier, actionKey: ActionKey): ActionInfo { + const actionInfoGetter = verifier[actionKey]; + if (isString(actionInfoGetter)) { + return getActionInfoOfVerfier(verifier, actionInfoGetter); + } + + if (isArray(actionInfoGetter)) { + const initialValue = getActionInfoOfVerfier(verifier, actionInfoGetter[0]); + return { + ...initialValue, + ...actionInfoGetter[1](initialValue) + }; + } + + return actionInfoGetter(); + } + function getActionInfo(verifiers: readonly DocumentPositionMapperVerifier[], actionKey: ActionKey): ActionInfo[] { - return verifiers.map(v => { - let actionInfoGetter = v[actionKey]; - while (isString(actionInfoGetter)) { - actionInfoGetter = v[actionInfoGetter]; - } - return actionInfoGetter(); - }); + return verifiers.map(v => getActionInfoOfVerfier(v, actionKey)); } interface VerifyAllFnAction { @@ -543,8 +566,8 @@ fn5(); session, host, openFiles(verifiers).map(f => f.path), - closedInfos(), - otherWatchedFiles(), + closedInfos, + otherWatchedFiles, debugInfo ); @@ -601,8 +624,8 @@ fn5(); session, host, openFiles(verifiers).map(f => f.path), - closedInfos(), - otherWatchedFiles(), + closedInfos, + otherWatchedFiles, debugInfo ); verifyDocumentPositionMapper({ @@ -887,360 +910,249 @@ ${dependencyTs.content}`); } describe("from project that uses dependency", () => { - function goToDefActionInfo(withRefs: boolean): ActionInfo { - return { - action: goToDefFromMainTs, - closedInfos: () => withRefs ? - [dependencyTs.path, dependencyConfig.path, libFile.path] : - [dependencyTs.path, libFile.path, dtsPath, dtsMapLocation], - otherWatchedFiles: () => [mainConfig.path], - expectsDts: !withRefs, // Dts script info present only if no project reference - expectsMap: !withRefs // Map script info present only if no project reference - }; - } - - function goToDefNoMapActionInfo(withRefs: boolean): ActionInfo { - return { - ...goToDefActionInfo(withRefs), - action: withRefs ? - goToDefFromMainTs : - goToDefFromMainTsWithNoMap, - closedInfos: () => withRefs ? - [dependencyTs.path, dependencyConfig.path, libFile.path] : - [libFile.path, dtsPath], // Because map is deleted, dts and dependency are released - otherWatchedFiles: () => withRefs ? - [mainConfig.path] : - [mainConfig.path, dtsMapPath], // Watches deleted file - expectsMap: false - }; - } - - function goToDefNoDtsActionInfo(withRefs: boolean): ActionInfo { - return { - ...goToDefActionInfo(withRefs), - action: withRefs ? - goToDefFromMainTs : - goToDefFromMainTsWithNoDts, - closedInfos: () => withRefs ? - [dependencyTs.path, dependencyConfig.path, libFile.path] : - [libFile.path], // No dts means no map, no dependency - expectsDts: false, - expectsMap: false - }; - } - verifyScenario({ mainScenario: "can go to definition correctly", verifier: withRefs => [ { ...goToDefFromMainTsProjectInfoVerifier(withRefs), - main: () => goToDefActionInfo(withRefs), + main: () => ({ + action: goToDefFromMainTs, + closedInfos: withRefs ? + [dependencyTs.path, dependencyConfig.path, libFile.path] : + [dependencyTs.path, libFile.path, dtsPath, dtsMapLocation], + otherWatchedFiles: [mainConfig.path], + expectsDts: !withRefs, // Dts script info present only if no project reference + expectsMap: !withRefs // Map script info present only if no project reference + }), change: "main", dtsChange: "main", - mapChange: () => ({ - ...goToDefActionInfo(withRefs), + mapChange: ["main", () => ({ freshDocumentMapper: true - }), - noMap: () => goToDefNoMapActionInfo(withRefs), + })], + noMap: withRefs ? + "main" : + ["main", main => ({ + action: goToDefFromMainTsWithNoMap, + // Because map is deleted, dts and dependency are released + closedInfos: removePath(main.closedInfos, dtsMapPath, dependencyTsPath), + // Watches deleted file + otherWatchedFiles: main.otherWatchedFiles.concat(dtsMapLocation), + expectsMap: false + })], mapFileCreated: "main", - mapFileDeleted: () => ({ - ...goToDefNoMapActionInfo(withRefs), - closedInfos: () => withRefs ? - [dependencyTs.path, dependencyConfig.path, libFile.path] : + mapFileDeleted: withRefs ? + "main" : + ["noMap", noMap => ({ // The script info for depedency is collected only after file open - [dependencyTs.path, libFile.path, dtsPath] - }), - noDts: () => goToDefNoDtsActionInfo(withRefs), + closedInfos: noMap.closedInfos.concat(dependencyTs.path) + })], + noDts: withRefs ? + "main" : + ["main", main => ({ + action: goToDefFromMainTsWithNoDts, + // No dts, no map, no dependency + closedInfos: removePath(main.closedInfos, dtsPath, dtsMapPath, dependencyTsPath), + expectsDts: false, + expectsMap: false + })], dtsFileCreated: "main", - dtsFileDeleted: () => ({ - ...goToDefNoDtsActionInfo(withRefs), - // The script info for map is collected only after file open - closedInfos: () => withRefs ? - [dependencyTs.path, dependencyConfig.path, libFile.path] : - [dependencyTs.path, libFile.path, dtsMapLocation], - expectsMap: !withRefs - }), - dependencyChange: () => ({ - ...goToDefActionInfo(withRefs), + dtsFileDeleted: withRefs ? + "main" : + ["noDts", noDts => ({ + // The script info for map is collected only after file open + closedInfos: noDts.closedInfos.concat(dependencyTs.path, dtsMapLocation), + expectsMap: true + })], + dependencyChange: ["main", () => ({ action: goToDefFromMainTsWithDependencyChange, - expectsDts: false, - expectsMap: false - }), - noBuild: "main" + })], + noBuild: "noDts" } ] }); }); describe("from defining project", () => { - function renameActionInfo(): ActionInfo { - return { - action: renameFromDependencyTs, - closedInfos: () => [libFile.path, dtsLocation, dtsMapLocation], - otherWatchedFiles: () => [dependencyConfig.path], - expectsDts: true, - expectsMap: true - }; - } - - function renameNoDtsActionInfo(): ActionInfo { - return { - action: renameFromDependencyTs, - closedInfos: () => [libFile.path], // no dts or map since dts itself doesnt exist - otherWatchedFiles: () => [dependencyConfig.path, dtsPath], // watch deleted file - expectsDts: false, - expectsMap: false - }; - } - verifyScenario({ mainScenario: "rename locations from dependency", verifier: () => [ { ...renameFromDependencyTsProjectInfoVerifier(), - main: renameActionInfo, - change: "main", - dtsChange: "main", - mapChange: () => ({ - ...renameActionInfo(), - freshDocumentMapper: true - }), - noMap: () => ({ - ...renameActionInfo(), - closedInfos: () => [libFile.path, dtsLocation], // No map - otherWatchedFiles: () => [dependencyConfig.path, dtsMapLocation], // watch map - expectsMap: false - }), - mapFileCreated: "main", - mapFileDeleted: "noMap", - noDts: renameNoDtsActionInfo, - dtsFileCreated: "main", - dtsFileDeleted: () => ({ - ...renameNoDtsActionInfo(), - // Map is collected after file open - closedInfos: () => [libFile.path, dtsMapLocation], + main: () => ({ + action: renameFromDependencyTs, + closedInfos: [libFile.path, dtsLocation, dtsMapLocation], + otherWatchedFiles: [dependencyConfig.path], + expectsDts: true, expectsMap: true }), - dependencyChange: () => ({ - ...renameActionInfo(), - action: renameFromDependencyTsWithDependencyChange - }), - noBuild: () => ({ - action: renameFromDependencyTs, - closedInfos: () => [libFile.path], // No dts or map since its not built/present - // Watching for creation of dts so that it can give correct results across projects - otherWatchedFiles: () => [dependencyConfig.path, dtsPath], + change: "main", + dtsChange: "main", + mapChange: ["main", () => ({ + freshDocumentMapper: true + })], + noMap: ["main", main => ({ + // No map + closedInfos: removePath(main.closedInfos, dtsMapPath), + // watch map + otherWatchedFiles: [...main.otherWatchedFiles, dtsMapLocation], + expectsMap: false + })], + mapFileCreated: "main", + mapFileDeleted: "noMap", + noDts: ["main", main => ({ + // no dts or map since dts itself doesnt exist + closedInfos: removePath(main.closedInfos, dtsMapPath, dtsPath), + // watch deleted file + otherWatchedFiles: [...main.otherWatchedFiles, dtsLocation], expectsDts: false, expectsMap: false - }) + })], + dtsFileCreated: "main", + dtsFileDeleted: ["noDts", noDts => ({ + // Map is collected after file open + closedInfos: noDts.closedInfos.concat(dtsMapLocation), + expectsMap: true + })], + dependencyChange: ["main", () => ({ + action: renameFromDependencyTsWithDependencyChange + })], + noBuild: "noDts" } ] }); }); describe("when opening depedency and usage project", () => { - function goToDefActionInfo(withRefs: boolean): ActionInfo { - return { - action: goToDefFromMainTs, - // DependencyTs is open, so omit it from closed infos - closedInfos: () => withRefs ? - [dependencyConfig.path, libFile.path] : - [libFile.path, dtsPath, dtsMapLocation], - otherWatchedFiles: () => withRefs ? - [mainConfig.path] : // Its in closed info - [mainConfig.path, dependencyConfig.path], - expectsDts: !withRefs, // Dts script info present only if no project reference - expectsMap: !withRefs // Map script info present only if no project reference - }; - } - - function renameActionInfo(withRefs: boolean): ActionInfo { - return { - action: renameFromDependencyTsWithBothProjectsOpen, - // DependencyTs is open, so omit it from closed infos - closedInfos: () => withRefs ? - [dependencyConfig.path, libFile.path, dtsLocation, dtsMapLocation] : - [libFile.path, dtsPath, dtsMapLocation], - otherWatchedFiles: () => withRefs ? - [mainConfig.path] : // Its in closed info - [mainConfig.path, dependencyConfig.path], - expectsDts: true, - expectsMap: true - }; - } - - function goToDefNoMapActionInfo(withRefs: boolean): ActionInfo { - return { - action: withRefs ? - goToDefFromMainTs : - goToDefFromMainTsWithNoMap, - closedInfos: () => withRefs ? - [dependencyConfig.path, libFile.path] : - [libFile.path, dtsPath], // No map - otherWatchedFiles: () => withRefs ? - [mainConfig.path] : // Its in closed info - [mainConfig.path, dependencyConfig.path, dtsMapLocation], // Watch map file - expectsDts: !withRefs, // Dts script info present only if no project reference - expectsMap: false - }; - } - - function renameNoMapActionInfo(withRefs: boolean): ActionInfo { - return { - ...renameActionInfo(withRefs), - action: withRefs ? - renameFromDependencyTsWithBothProjectsOpen : - renameFromDependencyTs, - closedInfos: () => withRefs ? - [dependencyConfig.path, libFile.path, dtsLocation] : - [libFile.path, dtsPath], // No map - otherWatchedFiles: () => withRefs ? - [mainConfig.path, dtsMapLocation] : // Its in closed info - [mainConfig.path, dependencyConfig.path, dtsMapLocation], // Watch map file - expectsMap: false - }; - } - - function goToDefNoDtsActionInfo(withRefs: boolean): ActionInfo { - return { - ...goToDefActionInfo(withRefs), - action: withRefs ? - goToDefFromMainTs : - goToDefFromMainTsWithNoDts, - closedInfos: () => withRefs ? - [dependencyConfig.path, libFile.path] : - [libFile.path], // No dts or map, - expectsDts: false, - expectsMap: false - }; - } - - function renameNoDtsActionInfo(withRefs: boolean): ActionInfo { - return { - action: withRefs ? - renameFromDependencyTsWithBothProjectsOpen : - renameFromDependencyTs, - closedInfos: () => withRefs ? - [dependencyConfig.path, libFile.path] : - [libFile.path], // No dts or map, - otherWatchedFiles: () => withRefs ? - [mainConfig.path, dtsLocation] : - [mainConfig.path, dependencyConfig.path, dtsPath], // Watch dts, - expectsDts: false, - expectsMap: false - }; - } - verifyScenario({ mainScenario: "goto Definition in usage and rename locations from defining project", verifier: withRefs => [ { ...goToDefFromMainTsProjectInfoVerifier(withRefs), - main: () => goToDefActionInfo(withRefs), - change: () => ({ - // Because before this rename is done the closed info remains same as rename's main operation - ...goToDefActionInfo(withRefs), - closedInfos: () => withRefs ? - [dependencyConfig.path, libFile.path, dtsLocation, dtsMapLocation] : + main: () => ({ + action: goToDefFromMainTs, + // DependencyTs is open, so omit it from closed infos + closedInfos: withRefs ? + [dependencyConfig.path, libFile.path] : [libFile.path, dtsPath, dtsMapLocation], - expectsDts: true, - expectsMap: true + otherWatchedFiles: withRefs ? + [mainConfig.path] : // Its in closed info + [mainConfig.path, dependencyConfig.path], + expectsDts: !withRefs, // Dts script info present only if no project reference + expectsMap: !withRefs // Map script info present only if no project reference }), + change: withRefs ? + ["main", main => ({ + // Because before this rename is done the closed info remains same as rename's main operation + closedInfos: main.closedInfos.concat(dtsLocation, dtsMapLocation), + expectsDts: true, + expectsMap: true + })] : + "main", dtsChange: "change", mapChange: "change", - noMap: () => goToDefNoMapActionInfo(withRefs), - mapFileCreated: () => ({ - // Because before this rename is done the closed info remains same as rename's main - ...goToDefActionInfo(withRefs), - closedInfos: () => withRefs ? - [dependencyConfig.path, libFile.path, dtsLocation] : - [libFile.path, dtsPath, dtsMapLocation], - expectsDts: true, - // This operation doesnt need map so the map info path in dts is not refreshed - skipDtsMapCheck: withRefs - }), - mapFileDeleted: () => ({ - // Because before this rename is done the closed info remains same as rename's noMap operation - ...goToDefNoMapActionInfo(withRefs), - closedInfos: () => withRefs ? - [dependencyConfig.path, libFile.path, dtsLocation] : - [libFile.path, dtsPath], // No map, - expectsDts: true, - // This operation doesnt need map so the map info path in dts is not refreshed - skipDtsMapCheck: withRefs - }), - noDts: () => goToDefNoDtsActionInfo(withRefs), - dtsFileCreated: () => ({ - ...goToDefActionInfo(withRefs), - // Since the project for dependency is not updated, the watcher from rename for dts still there - otherWatchedFiles: () => withRefs ? - [mainConfig.path, dtsLocation] : - [mainConfig.path, dependencyConfig.path], - }), - dtsFileDeleted: () => ({ - ...goToDefNoDtsActionInfo(withRefs), + noMap: withRefs ? + "main" : + ["main", main => ({ + action: goToDefFromMainTsWithNoMap, + closedInfos: removePath(main.closedInfos, dtsMapPath), + otherWatchedFiles: main.otherWatchedFiles.concat(dtsMapLocation), + expectsMap: false + })], + mapFileCreated: withRefs ? + ["main", main => ({ + // Because before this rename is done the closed info remains same as rename's main + closedInfos: main.closedInfos.concat(dtsLocation), + expectsDts: true, + // This operation doesnt need map so the map info path in dts is not refreshed + skipDtsMapCheck: withRefs + })] : + "main", + mapFileDeleted: withRefs ? + ["noMap", noMap => ({ + // Because before this rename is done the closed info remains same as rename's noMap operation + closedInfos: noMap.closedInfos.concat(dtsLocation), + expectsDts: true, + // This operation doesnt need map so the map info path in dts is not refreshed + skipDtsMapCheck: true + })] : + "noMap", + noDts: withRefs ? + "main" : + ["main", main => ({ + action: goToDefFromMainTsWithNoDts, + closedInfos: removePath(main.closedInfos, dtsMapPath, dtsPath), + expectsDts: false, + expectsMap: false + })], + dtsFileCreated: withRefs ? + ["main", main => ({ + // Since the project for dependency is not updated, the watcher from rename for dts still there + otherWatchedFiles: main.otherWatchedFiles.concat(dtsLocation) + })] : + "main", + dtsFileDeleted: ["noDts", noDts => ({ // Map collection after file open - closedInfos: () => withRefs ? - [dependencyConfig.path, libFile.path, dtsMapLocation] : - [libFile.path, dtsMapLocation], + closedInfos: noDts.closedInfos.concat(dtsMapLocation), expectsMap: true - }), - dependencyChange: () => ({ - ...goToDefActionInfo(withRefs), + })], + dependencyChange: ["change", () => ({ action: goToDefFromMainTsWithDependencyChange, - // From rename main action - closedInfos: () => withRefs ? - [dependencyConfig.path, libFile.path, dtsLocation, dtsMapLocation] : - [libFile.path, dtsPath, dtsMapLocation], - expectsDts: withRefs, - expectsMap: withRefs - }), - noBuild: "main" + })], + noBuild: "noDts" }, { ...renameFromDependencyTsProjectInfoVerifier(), main: () => ({ - ...renameActionInfo(withRefs), + action: renameFromDependencyTsWithBothProjectsOpen, + // DependencyTs is open, so omit it from closed infos + closedInfos: withRefs ? + [dependencyConfig.path, libFile.path, dtsLocation, dtsMapLocation] : + [libFile.path, dtsPath, dtsMapLocation], + otherWatchedFiles: withRefs ? + [mainConfig.path] : // Its in closed info + [mainConfig.path, dependencyConfig.path], + expectsDts: true, + expectsMap: true, freshMapInfo: withRefs }), - change: () => renameActionInfo(withRefs), + change: ["main", () => ({ + freshMapInfo: false + })], dtsChange: "change", - mapChange: () => ({ - ...renameActionInfo(withRefs), + mapChange: ["main", () => ({ + freshMapInfo: false, freshDocumentMapper: withRefs - }), - noMap: () => ({ - ...renameNoMapActionInfo(withRefs), - freshMapInfo: withRefs, + })], + noMap: ["main", main => ({ + action: withRefs ? + renameFromDependencyTsWithBothProjectsOpen : + renameFromDependencyTs, + closedInfos: removePath(main.closedInfos, dtsMapPath), + otherWatchedFiles: main.otherWatchedFiles.concat(dtsMapLocation), + expectsMap: false, freshDocumentMapper: withRefs - }), + })], mapFileCreated: "main", mapFileDeleted: "noMap", - noDts: () => renameNoDtsActionInfo(withRefs), - dtsFileCreated: "main", - dtsFileDeleted: () => ({ - ...renameNoDtsActionInfo(withRefs), - // Map collection after file open - closedInfos: () => withRefs ? - [dependencyConfig.path, libFile.path, dtsMapLocation] : - [libFile.path, dtsMapLocation], - expectsMap: true - }), - dependencyChange: () => ({ - ...renameActionInfo(withRefs), - action: renameFromDependencyTsWithBothProjectsOpenWithDependencyChange - }), - noBuild: () => ({ - action: renameFromDependencyTsWithBothProjectsOpen, - closedInfos: () => withRefs ? - [dependencyConfig.path, libFile.path] : - [libFile.path], - otherWatchedFiles: () => withRefs ? - [mainConfig.path, dtsLocation] : // Its in closed info - [mainConfig.path, dependencyConfig.path], + noDts: ["change", change => ({ + action: withRefs ? + renameFromDependencyTsWithBothProjectsOpen : + renameFromDependencyTs, + closedInfos: removePath(change.closedInfos, dtsPath, dtsMapPath), + otherWatchedFiles: change.otherWatchedFiles.concat(dtsLocation), expectsDts: false, expectsMap: false - }) + })], + dtsFileCreated: "main", + dtsFileDeleted: ["noDts", noDts => ({ + // Map collection after file open + closedInfos: noDts.closedInfos.concat(dtsMapLocation) , + expectsMap: true + })], + dependencyChange: ["change", () => ({ + action: renameFromDependencyTsWithBothProjectsOpenWithDependencyChange + })], + noBuild: "noDts" } ] }); From b1fa2ebff5bfcd8bc69932cbec01d0c1479f091f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 5 Jul 2019 12:17:01 -0700 Subject: [PATCH 18/25] Errors using DiagnosticsSync commands --- .../tsserver/projectReferenceErrors.ts | 88 ++++++++++++++++--- 1 file changed, 74 insertions(+), 14 deletions(-) diff --git a/src/testRunner/unittests/tsserver/projectReferenceErrors.ts b/src/testRunner/unittests/tsserver/projectReferenceErrors.ts index 8006b91603..303808cb4e 100644 --- a/src/testRunner/unittests/tsserver/projectReferenceErrors.ts +++ b/src/testRunner/unittests/tsserver/projectReferenceErrors.ts @@ -61,7 +61,7 @@ fnErr(); interface CheckAllErrors { session: TestSession; host: TestServerHost; - expected: ReadonlyArray; + expected: readonly GetErrDiagnostics[]; expectedSequenceId: number; } function checkAllErrors({ session, host, expected, expectedSequenceId }: CheckAllErrors) { @@ -118,6 +118,40 @@ fnErr(); }); } + function verifyErrorsUsingSyncMethods({ openFiles, expectedSyncDiagnostics }: VerifyScenario) { + it("verifies the errors using sync commands", () => { + const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); + const session = createSession(host); + openFilesForSession(openFiles(), session); + for (const { file, project, syntax, semantic, suggestion } of expectedSyncDiagnostics()) { + const actualSyntax = session.executeCommandSeq({ + 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({ + 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({ + 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}`); + } + }); + } + interface GetErrDiagnostics { file: File; syntax: protocol.Diagnostic[]; @@ -126,16 +160,21 @@ fnErr(); } interface GetErrForProjectDiagnostics { project: string; - errors: ReadonlyArray; + errors: readonly GetErrDiagnostics[]; + } + interface SyncDiagnostics extends GetErrDiagnostics { + project?: string; } interface VerifyScenario { - openFiles: () => ReadonlyArray; - expectedGetErr: () => ReadonlyArray; - expectedGetErrForProject: () => ReadonlyArray; + openFiles: () => readonly File[]; + expectedGetErr: () => readonly GetErrDiagnostics[]; + expectedGetErrForProject: () => readonly GetErrForProjectDiagnostics[]; + expectedSyncDiagnostics: () => readonly SyncDiagnostics[]; } function verifyScenario(scenario: VerifyScenario) { verifyErrorsUsingGeterr(scenario); verifyErrorsUsingGeterrForProject(scenario); + verifyErrorsUsingSyncMethods(scenario); } function emptyDiagnostics(file: File): GetErrDiagnostics { @@ -169,13 +208,13 @@ fnErr(); 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", - ) + createDiagnostic( + { line: 6, offset: 12 }, + { line: 6, offset: 13 }, + Diagnostics.Type_0_is_not_assignable_to_type_1, + ["10", "string"], + "error", + ) ], suggestion: emptyArray }; @@ -200,6 +239,10 @@ fnErr(); }; } + function syncDiagnostics(diagnostics: GetErrDiagnostics, project: string): SyncDiagnostics { + return { project, ...diagnostics }; + } + describe("when dependency project is not open", () => { verifyScenario({ openFiles: () => [usageTs], @@ -215,7 +258,15 @@ fnErr(); usageDiagnostics() ] } - ] + ], + expectedSyncDiagnostics: () => [ + // Without project + usageDiagnostics(), + emptyDiagnostics(dependencyTs), + // With project + syncDiagnostics(usageDiagnostics(), usageConfig.path), + syncDiagnostics(emptyDiagnostics(dependencyTs), usageConfig.path), + ], }); }); @@ -229,7 +280,16 @@ fnErr(); expectedGetErrForProject: () => [ usageProjectDiagnostics(), dependencyProjectDiagnostics() - ] + ], + expectedSyncDiagnostics: () => [ + // Without project + usageDiagnostics(), + dependencyDiagnostics(), + // With project + syncDiagnostics(usageDiagnostics(), usageConfig.path), + syncDiagnostics(emptyDiagnostics(dependencyTs), usageConfig.path), + syncDiagnostics(dependencyDiagnostics(), dependencyConfig.path), + ], }); }); }); From 824c22c460b9a57b9c67398890449cd6799fb4e0 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 8 Jul 2019 15:55:35 -0700 Subject: [PATCH 19/25] Source of project reference behave as if those files cannot be emitted. --- src/compiler/sys.ts | 47 +- src/harness/virtualFileSystemWithWatch.ts | 15 + src/server/project.ts | 9 +- src/server/session.ts | 4 +- src/testRunner/tsconfig.json | 1 + src/testRunner/unittests/tsbuildWatchMode.ts | 14 +- src/testRunner/unittests/tsserver/helpers.ts | 2 +- .../tsserver/projectReferenceCompileOnSave.ts | 410 ++++++++++++++++++ .../reference/api/tsserverlibrary.d.ts | 1 - 9 files changed, 468 insertions(+), 35 deletions(-) create mode 100644 src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 5abc19cdbc..b7eca0ee51 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -472,6 +472,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*/ interface NodeBuffer extends Uint8Array { write(str: string, offset?: number, length?: number, encoding?: string): number; @@ -1259,17 +1286,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(); @@ -1281,14 +1297,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!; })(); diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index 9e615a0263..23127f49aa 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -66,6 +66,8 @@ interface Array {}` params.newLine, params.useWindowsStylePaths, params.environmentVariables); + // Just like sys, patch the host to use writeFile + patchWriteFileEnsuringDirectory(host); return host; } @@ -990,6 +992,19 @@ interface Array {}` } } + export type TestServerHostTrackingWrittenFiles = TestServerHost & { writtenFiles: Map; }; + + export function changeToHostTrackingWrittenFiles(inputHost: TestServerHost) { + const host = inputHost as TestServerHostTrackingWrittenFiles; + const originalWriteFile = host.writeFile; + host.writtenFiles = createMap(); + 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}`; diff --git a/src/server/project.ts b/src/server/project.ts index cd5a3fec85..2811b04c41 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -537,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[] { @@ -548,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); } /** diff --git a/src/server/session.ts b/src/server/session.ts index a5c1357480..ff08099855 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1030,7 +1030,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: ReadonlyArray, project: Project): ReadonlyArray { diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 9d67215ffd..ee2aba6275 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -137,6 +137,7 @@ "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", diff --git a/src/testRunner/unittests/tsbuildWatchMode.ts b/src/testRunner/unittests/tsbuildWatchMode.ts index 6a577888c7..dcc2fdbb1a 100644 --- a/src/testRunner/unittests/tsbuildWatchMode.ts +++ b/src/testRunner/unittests/tsbuildWatchMode.ts @@ -2,18 +2,12 @@ namespace ts.tscWatch { import projectsLocation = TestFSWithWatch.tsbuildProjectsLocation; import getFilePathInProject = TestFSWithWatch.getTsBuildProjectFilePath; import getFileFromProject = TestFSWithWatch.getTsBuildProjectFile; - type TsBuildWatchSystem = WatchedSystem & { writtenFiles: Map; }; + type TsBuildWatchSystem = TestFSWithWatch.TestServerHostTrackingWrittenFiles; function createTsBuildWatchSystem(fileOrFolderList: ReadonlyArray, params?: TestFSWithWatch.TestServerHostCreationParameters) { - const host = createWatchedSystem(fileOrFolderList, params) as TsBuildWatchSystem; - const originalWriteFile = host.writeFile; - host.writtenFiles = createMap(); - 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: ReadonlyArray, defaultOptions?: BuildOptions) { diff --git a/src/testRunner/unittests/tsserver/helpers.ts b/src/testRunner/unittests/tsserver/helpers.ts index 1c6bd4e9f5..12611f7926 100644 --- a/src/testRunner/unittests/tsserver/helpers.ts +++ b/src/testRunner/unittests/tsserver/helpers.ts @@ -498,7 +498,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); diff --git a/src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts b/src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts new file mode 100644 index 0000000000..9602ef6360 --- /dev/null +++ b/src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts @@ -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({ + 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({ + 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({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: args + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, expectedAffected, "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + 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({ + 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(), + ] + }); + }); + }); +} diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index ea7003e471..0b728ac95d 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -8463,7 +8463,6 @@ declare namespace ts.server { getGlobalProjectErrors(): ReadonlyArray; getAllProjectErrors(): ReadonlyArray; getLanguageService(ensureSynchronized?: boolean): LanguageService; - private shouldEmitFile; getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[]; /** * Returns true if emit was conducted From b63185097889376b4c02094b863af9cf752f34cc Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 10 Jul 2019 15:21:24 -0700 Subject: [PATCH 20/25] Add option disableSourceOfProjectReferenceRedirect to disable using sources of project reference redirect from editor --- src/compiler/commandLineParser.ts | 6 ++ src/compiler/diagnosticMessages.json | 4 + src/compiler/program.ts | 12 +-- src/compiler/types.ts | 3 +- src/server/editorServices.ts | 2 +- src/server/project.ts | 7 +- src/services/services.ts | 4 +- src/services/types.ts | 2 +- .../tsserver/events/projectLoading.ts | 84 ++++++++++++------- .../unittests/tsserver/projectReferences.ts | 30 +++++-- .../reference/api/tsserverlibrary.d.ts | 1 + tests/baselines/reference/api/typescript.d.ts | 1 + .../tsconfig.json | 5 ++ 13 files changed, 110 insertions(+), 51 deletions(-) create mode 100644 tests/baselines/reference/showConfig/Shows tsconfig for single option/disableSourceOfProjectReferenceRedirect/tsconfig.json diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 4747b89f08..7f67b9c509 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -751,6 +751,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_using_source_of_project_reference_redirect_files + }, { name: "noImplicitUseStrict", type: "boolean", diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 7d178f1d47..edc3903b38 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3967,6 +3967,10 @@ "category": "Message", "code": 6220 }, + "Disable using source of project reference redirect files.": { + "category": "Message", + "code": 6221 + }, "Projects to reference": { "category": "Message", diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 2149e175ca..63f5806617 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -814,7 +814,7 @@ namespace ts { let projectReferenceRedirects: Map | undefined; let mapFromFileToProjectReferenceRedirects: Map | undefined; let mapFromToProjectReferenceRedirectSource: Map | undefined; - const useSourceOfReference = !!host.useSourceInsteadOfReferenceRedirect && host.useSourceInsteadOfReferenceRedirect(); + const useSourceOfProjectReferenceRedirect = !!host.useSourceOfProjectReferenceRedirect && host.useSourceOfProjectReferenceRedirect(); const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options); const structuralIsReused = tryReuseStructureFromOldProgram(); @@ -836,7 +836,7 @@ namespace ts { for (const parsedRef of resolvedProjectReferences) { if (!parsedRef) continue; const out = parsedRef.commandLine.options.outFile || parsedRef.commandLine.options.out; - if (useSourceOfReference) { + 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); @@ -1418,7 +1418,7 @@ namespace ts { for (const newSourceFile of newSourceFiles) { const filePath = newSourceFile.path; addFileToFilesByName(newSourceFile, filePath, newSourceFile.resolvedPath); - if (useSourceOfReference) { + if (useSourceOfProjectReferenceRedirect) { const redirectProject = getProjectReferenceRedirectProject(newSourceFile.fileName); if (redirectProject && !(redirectProject.commandLine.options.outFile || redirectProject.commandLine.options.out)) { const redirect = getProjectReferenceOutputName(redirectProject, newSourceFile.fileName); @@ -2252,7 +2252,7 @@ namespace ts { // Get source file from normalized fileName function findSourceFile(fileName: string, path: Path, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, refFile: SourceFile, refPos: number, refEnd: number, packageId: PackageId | undefined): SourceFile | undefined { - if (useSourceOfReference) { + if (useSourceOfProjectReferenceRedirect) { const source = getSourceOfProjectReferenceRedirect(fileName); if (source) { const file = isString(source) ? @@ -2309,7 +2309,7 @@ namespace ts { } let redirectedPath: Path | undefined; - if (refFile && !useSourceOfReference) { + if (refFile && !useSourceOfProjectReferenceRedirect) { const redirectProject = getProjectReferenceRedirectProject(fileName); if (redirectProject) { if (redirectProject.commandLine.options.outFile || redirectProject.commandLine.options.out) { @@ -2498,7 +2498,7 @@ namespace ts { } function isSourceOfProjectReferenceRedirect(fileName: string) { - return useSourceOfReference && !!getResolvedProjectReferenceToRedirect(fileName); + return useSourceOfProjectReferenceRedirect && !!getResolvedProjectReferenceToRedirect(fileName); } function forEachProjectReference( diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 16ea47beec..69ac55e6e1 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4645,6 +4645,7 @@ namespace ts { /* @internal */ diagnostics?: boolean; /* @internal */ extendedDiagnostics?: boolean; disableSizeLimit?: boolean; + disableSourceOfProjectReferenceRedirect?: boolean; downlevelIteration?: boolean; emitBOM?: boolean; emitDecoratorMetadata?: boolean; @@ -5169,7 +5170,7 @@ namespace ts { createHash?(data: string): string; getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined; /* @internal */ setResolvedProjectReferenceCallbacks?(callbacks: ResolvedProjectReferenceCallbacks): void; - /* @internal */ useSourceInsteadOfReferenceRedirect?(): boolean; + /* @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; diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 6fea0bc417..ab325d5ed3 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -2583,7 +2583,7 @@ namespace ts.server { if (!configFileName) return undefined; const configuredProject = this.findConfiguredProjectByProjectName(configFileName) || - this.createAndLoadConfiguredProject(configFileName, `Creating project for original file: ${originalFileInfo.fileName}${location !== originalLocation ? " 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 diff --git a/src/server/project.ts b/src/server/project.ts index 2811b04c41..87ced54de8 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1540,11 +1540,12 @@ namespace ts.server { } /* @internal */ - useSourceInsteadOfReferenceRedirect = () => !!this.languageServiceEnabled; + useSourceOfProjectReferenceRedirect = () => !!this.languageServiceEnabled && + !this.getCompilerOptions().disableSourceOfProjectReferenceRedirect; fileExists(file: string): boolean { // Project references go to source file instead of .d.ts file - if (this.useSourceInsteadOfReferenceRedirect() && this.projectReferenceCallbacks) { + if (this.useSourceOfProjectReferenceRedirect() && this.projectReferenceCallbacks) { const source = this.projectReferenceCallbacks.getSourceOfProjectReferenceRedirect(file); if (source) return isString(source) ? super.fileExists(source) : true; } @@ -1553,7 +1554,7 @@ namespace ts.server { directoryExists(path: string): boolean { if (super.directoryExists(path)) return true; - if (!this.useSourceInsteadOfReferenceRedirect() || !this.projectReferenceCallbacks) return false; + if (!this.useSourceOfProjectReferenceRedirect() || !this.projectReferenceCallbacks) return false; if (!this.mapOfDeclarationDirectories) { this.mapOfDeclarationDirectories = createMap(); diff --git a/src/services/services.ts b/src/services/services.ts index 2f9ba6eec9..c4af3c9470 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1248,8 +1248,8 @@ namespace ts { if (host.setResolvedProjectReferenceCallbacks) { compilerHost.setResolvedProjectReferenceCallbacks = callbacks => host.setResolvedProjectReferenceCallbacks!(callbacks); } - if (host.useSourceInsteadOfReferenceRedirect) { - compilerHost.useSourceInsteadOfReferenceRedirect = () => host.useSourceInsteadOfReferenceRedirect!(); + if (host.useSourceOfProjectReferenceRedirect) { + compilerHost.useSourceOfProjectReferenceRedirect = () => host.useSourceOfProjectReferenceRedirect!(); } const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings); diff --git a/src/services/types.ts b/src/services/types.ts index b4fc25e587..ec71813e1d 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -239,7 +239,7 @@ namespace ts { /* @internal */ setResolvedProjectReferenceCallbacks?(callbacks: ResolvedProjectReferenceCallbacks): void; /* @internal */ - useSourceInsteadOfReferenceRedirect?(): boolean; + useSourceOfProjectReferenceRedirect?(): boolean; } /* @internal */ diff --git a/src/testRunner/unittests/tsserver/events/projectLoading.ts b/src/testRunner/unittests/tsserver/events/projectLoading.ts index 53fe28240f..3cc25232bc 100644 --- a/src/testRunner/unittests/tsserver/events/projectLoading.ts +++ b/src/testRunner/unittests/tsserver/events/projectLoading.ts @@ -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({ - command: protocol.CommandTypes.References, - arguments: { - file: bTs.path, - ...protocolLocationFromSubstring(bTs.content, "A()") - } - }); + session.executeCommandSeq({ + 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}`); + 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 ", () => { diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index f9445dc6d1..fe1ff10aac 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -417,6 +417,7 @@ fn5(); interface VerifierAndWithRefs { withRefs: boolean; + disableSourceOfProjectReferenceRedirect?: true; verifier: (withRefs: boolean) => readonly DocumentPositionMapperVerifier[]; } @@ -426,7 +427,7 @@ fn5(); interface OpenTsFile extends VerifierAndWithRefs { onHostCreate?: (host: TestServerHost) => void; } - function openTsFile({ withRefs, verifier, onHostCreate }: OpenTsFile) { + function openTsFile({ withRefs, disableSourceOfProjectReferenceRedirect, verifier, onHostCreate }: OpenTsFile) { const host = createHost(files, [mainConfig.path]); if (!withRefs) { // Erase project reference @@ -434,11 +435,22 @@ fn5(); compilerOptions: { composite: true, declarationMap: true } })); } + else if (disableSourceOfProjectReferenceRedirect) { + // Erase project reference + host.writeFile(mainConfig.path, JSON.stringify({ + compilerOptions: { + composite: true, + declarationMap: true, + disableSourceOfProjectReferenceRedirect: !!disableSourceOfProjectReferenceRedirect + }, + references: [{ path: "../dependency" }] + })); + } if (onHostCreate) { onHostCreate(host); } const session = createSession(host); - const verifiers = verifier(withRefs); + const verifiers = verifier(withRefs && !disableSourceOfProjectReferenceRedirect); openFilesForSession([...openFiles(verifiers), randomFile], session); return { host, session, verifiers }; } @@ -786,9 +798,9 @@ fn5(); }); } - function verifyScenarioWorker({ mainScenario, verifier }: VerifyScenario, withRefs: boolean) { + function verifyScenarioWorker({ mainScenario, verifier }: VerifyScenario, withRefs: boolean, disableSourceOfProjectReferenceRedirect?: true) { it(mainScenario, () => { - const { host, session, verifiers } = openTsFile({ withRefs, verifier }); + const { host, session, verifiers } = openTsFile({ withRefs, disableSourceOfProjectReferenceRedirect, verifier }); checkProject(session, verifiers); verifyScenarioAndScriptInfoCollection(session, host, verifiers, "main"); }); @@ -798,6 +810,7 @@ fn5(); scenarioName: "when usage file changes, document position mapper doesnt change", verifier, withRefs, + disableSourceOfProjectReferenceRedirect, change: (_host, session, verifiers) => verifiers.forEach( verifier => session.executeCommandSeq({ command: protocol.CommandTypes.Change, @@ -819,6 +832,7 @@ fn5(); scenarioName: "when dependency .d.ts changes, document position mapper doesnt change", verifier, withRefs, + disableSourceOfProjectReferenceRedirect, change: host => host.writeFile( dtsLocation, host.readFile(dtsLocation)!.replace( @@ -835,6 +849,7 @@ fn5(); scenarioName: "when dependency file's map changes", verifier, withRefs, + disableSourceOfProjectReferenceRedirect, change: host => host.writeFile( dtsMapLocation, `{"version":3,"file":"FnS.d.ts","sourceRoot":"","sources":["../dependency/FnS.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,eAAO,MAAM,CAAC,KAAK,CAAC"}` @@ -846,6 +861,7 @@ fn5(); scenarioName: "with depedency files map file", verifier, withRefs, + disableSourceOfProjectReferenceRedirect, fileLocation: dtsMapLocation, fileNotPresentKey: "noMap", fileCreatedKey: "mapFileCreated", @@ -856,6 +872,7 @@ fn5(); scenarioName: "with depedency .d.ts file", verifier, withRefs, + disableSourceOfProjectReferenceRedirect, fileLocation: dtsLocation, fileNotPresentKey: "noDts", fileCreatedKey: "dtsFileCreated", @@ -863,7 +880,7 @@ fn5(); noDts: true }); - if (withRefs) { + if (withRefs && !disableSourceOfProjectReferenceRedirect) { verifyScenarioWithChanges({ scenarioName: "when defining project source changes", verifier, @@ -907,6 +924,9 @@ ${dependencyTs.content}`); describe("when main tsconfig has project reference", () => { verifyScenarioWorker(scenario, /*withRefs*/ true); }); + describe("when main tsconfig has but has disableSourceOfProjectReferenceRedirect", () => { + verifyScenarioWorker(scenario, /*withRefs*/ true); + }); } describe("from project that uses dependency", () => { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 0b728ac95d..09931908ed 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2513,6 +2513,7 @@ declare namespace ts { emitDeclarationOnly?: boolean; declarationDir?: string; disableSizeLimit?: boolean; + disableSourceOfProjectReferenceRedirect?: boolean; downlevelIteration?: boolean; emitBOM?: boolean; emitDecoratorMetadata?: boolean; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index be8eea8062..34f6fd36da 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2513,6 +2513,7 @@ declare namespace ts { emitDeclarationOnly?: boolean; declarationDir?: string; disableSizeLimit?: boolean; + disableSourceOfProjectReferenceRedirect?: boolean; downlevelIteration?: boolean; emitBOM?: boolean; emitDecoratorMetadata?: boolean; diff --git a/tests/baselines/reference/showConfig/Shows tsconfig for single option/disableSourceOfProjectReferenceRedirect/tsconfig.json b/tests/baselines/reference/showConfig/Shows tsconfig for single option/disableSourceOfProjectReferenceRedirect/tsconfig.json new file mode 100644 index 0000000000..c8b95e0909 --- /dev/null +++ b/tests/baselines/reference/showConfig/Shows tsconfig for single option/disableSourceOfProjectReferenceRedirect/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "disableSourceOfProjectReferenceRedirect": true + } +} From c6e502be7d4682250b7ad8d88eb77f8187a719dc Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 22 Aug 2019 11:26:26 -0700 Subject: [PATCH 21/25] Verify config file errors --- .../tsserver/projectReferenceErrors.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/testRunner/unittests/tsserver/projectReferenceErrors.ts b/src/testRunner/unittests/tsserver/projectReferenceErrors.ts index 303808cb4e..62468fcc6f 100644 --- a/src/testRunner/unittests/tsserver/projectReferenceErrors.ts +++ b/src/testRunner/unittests/tsserver/projectReferenceErrors.ts @@ -152,6 +152,25 @@ fnErr(); }); } + function verifyConfigFileErrors({ openFiles, expectedConfigFileDiagEvents }: VerifyScenario) { + it("verify config file errors", () => { + const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); + const { session, events } = createSessionWithEventTracking(host, server.ConfigFileDiagEvent); + + for (const file of openFiles()) { + session.executeCommandSeq({ + 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[]; @@ -170,11 +189,13 @@ fnErr(); 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 { @@ -243,6 +264,22 @@ fnErr(); return { project, ...diagnostics }; } + 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({ openFiles: () => [usageTs], @@ -267,6 +304,9 @@ fnErr(); syncDiagnostics(usageDiagnostics(), usageConfig.path), syncDiagnostics(emptyDiagnostics(dependencyTs), usageConfig.path), ], + expectedConfigFileDiagEvents: () => [ + usageConfigDiag() + ], }); }); @@ -290,6 +330,10 @@ fnErr(); syncDiagnostics(emptyDiagnostics(dependencyTs), usageConfig.path), syncDiagnostics(dependencyDiagnostics(), dependencyConfig.path), ], + expectedConfigFileDiagEvents: () => [ + usageConfigDiag(), + dependencyConfigDiag() + ], }); }); }); From 076dde482045de6ff7189d75459a5aab756b4336 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 22 Aug 2019 12:59:08 -0700 Subject: [PATCH 22/25] Test with --out as well --- .../tsserver/projectReferenceErrors.ts | 399 +++++++++++------- 1 file changed, 244 insertions(+), 155 deletions(-) diff --git a/src/testRunner/unittests/tsserver/projectReferenceErrors.ts b/src/testRunner/unittests/tsserver/projectReferenceErrors.ts index 62468fcc6f..ac023743da 100644 --- a/src/testRunner/unittests/tsserver/projectReferenceErrors.ts +++ b/src/testRunner/unittests/tsserver/projectReferenceErrors.ts @@ -3,37 +3,6 @@ namespace ts.projectSystem { 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() { } -// 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({ - references: [{ path: "../dependency" }] - }) - }; interface CheckErrorsInFile { session: TestSession; @@ -75,9 +44,9 @@ fnErr(); } } - function verifyErrorsUsingGeterr({ openFiles, expectedGetErr }: VerifyScenario) { + function verifyErrorsUsingGeterr({ allFiles, openFiles, expectedGetErr }: VerifyScenario) { it("verifies the errors in open file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); + const host = createServerHost([...allFiles(), libFile]); const session = createSession(host, { canUseEvents: true, }); openFilesForSession(openFiles(), session); @@ -96,9 +65,9 @@ fnErr(); }); } - function verifyErrorsUsingGeterrForProject({ openFiles, expectedGetErrForProject }: VerifyScenario) { + function verifyErrorsUsingGeterrForProject({ allFiles, openFiles, expectedGetErrForProject }: VerifyScenario) { it("verifies the errors in projects", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); + const host = createServerHost([...allFiles(), libFile]); const session = createSession(host, { canUseEvents: true, }); openFilesForSession(openFiles(), session); @@ -118,9 +87,9 @@ fnErr(); }); } - function verifyErrorsUsingSyncMethods({ openFiles, expectedSyncDiagnostics }: VerifyScenario) { + function verifyErrorsUsingSyncMethods({ allFiles, openFiles, expectedSyncDiagnostics }: VerifyScenario) { it("verifies the errors using sync commands", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); + const host = createServerHost([...allFiles(), libFile]); const session = createSession(host); openFilesForSession(openFiles(), session); for (const { file, project, syntax, semantic, suggestion } of expectedSyncDiagnostics()) { @@ -152,9 +121,9 @@ fnErr(); }); } - function verifyConfigFileErrors({ openFiles, expectedConfigFileDiagEvents }: VerifyScenario) { + function verifyConfigFileErrors({ allFiles, openFiles, expectedConfigFileDiagEvents }: VerifyScenario) { it("verify config file errors", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); + const host = createServerHost([...allFiles(), libFile]); const { session, events } = createSessionWithEventTracking(host, server.ConfigFileDiagEvent); for (const file of openFiles()) { @@ -185,6 +154,7 @@ fnErr(); project?: string; } interface VerifyScenario { + allFiles: () => readonly File[]; openFiles: () => readonly File[]; expectedGetErr: () => readonly GetErrDiagnostics[]; expectedGetErrForProject: () => readonly GetErrForProjectDiagnostics[]; @@ -207,133 +177,252 @@ fnErr(); }; } - 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 - }; - } - - function usageProjectDiagnostics(): GetErrForProjectDiagnostics { - return { - project: usageTs.path, - errors: [ - usageDiagnostics(), - emptyDiagnostics(dependencyTs) - ] - }; - } - - function dependencyProjectDiagnostics(): GetErrForProjectDiagnostics { - return { - project: dependencyTs.path, - errors: [ - dependencyDiagnostics() - ] - }; - } - function syncDiagnostics(diagnostics: GetErrDiagnostics, project: string): SyncDiagnostics { return { project, ...diagnostics }; } - function usageConfigDiag(): server.ConfigFileDiagEvent["data"] { - return { - triggerFile: usageTs.path, - configFileName: usageConfig.path, - diagnostics: emptyArray - }; + 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() + ], + }); + }); } - function dependencyConfigDiag(): server.ConfigFileDiagEvent["data"] { - return { - triggerFile: dependencyTs.path, - configFileName: dependencyConfig.path, - diagnostics: emptyArray + 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({ + 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 + }; + } - describe("when dependency project is not open", () => { - verifyScenario({ - 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() - ], + 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("when the depedency file is open", () => { - verifyScenario({ - 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 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: { 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 }); }); }); From a469fd82b9f33071841da9c2a38237915c105c11 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 22 Aug 2019 13:14:50 -0700 Subject: [PATCH 23/25] Should not report that files are not part of config for files that are not going to be emitted --- src/compiler/program.ts | 11 ++++++++--- .../unittests/tsserver/projectReferenceErrors.ts | 3 ++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 62f003910a..45918e26e3 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1008,9 +1008,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); @@ -2933,8 +2939,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, diff --git a/src/testRunner/unittests/tsserver/projectReferenceErrors.ts b/src/testRunner/unittests/tsserver/projectReferenceErrors.ts index ac023743da..a3ec484861 100644 --- a/src/testRunner/unittests/tsserver/projectReferenceErrors.ts +++ b/src/testRunner/unittests/tsserver/projectReferenceErrors.ts @@ -313,6 +313,7 @@ fnErr(); const usageConfig: File = { path: `${usageLocation}/tsconfig.json`, content: JSON.stringify({ + compilerOptions: { composite: true }, references: [{ path: "../dependency" }] }) }; @@ -381,7 +382,7 @@ fnErr(); const usageConfig: File = { path: `${usageLocation}/tsconfig.json`, content: JSON.stringify({ - compilerOptions: { outFile: "../usage.js" }, + compilerOptions: { composite: true, outFile: "../usage.js" }, references: [{ path: "../dependency" }] }) }; From 432da939c1d7d9f82e21b89224960fea34b44f63 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 23 Sep 2019 13:54:12 -0700 Subject: [PATCH 24/25] Add doc comments for fileExists and directoryExists implementation --- src/server/project.ts | 10 ++++++++++ tests/baselines/reference/api/tsserverlibrary.d.ts | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/server/project.ts b/src/server/project.ts index 080d3ee54c..418532bdc4 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1549,6 +1549,11 @@ namespace ts.server { 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) { @@ -1558,6 +1563,11 @@ namespace ts.server { 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; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 1a3e9baf94..d97f62d0ba 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -8576,7 +8576,17 @@ declare namespace ts.server { 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 From fd3ba679ec174180acac5c89e572a8ccba97870b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 24 Sep 2019 12:22:42 -0700 Subject: [PATCH 25/25] Reword the option description per feedback --- src/compiler/commandLineParser.ts | 2 +- src/compiler/diagnosticMessages.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index ca113fb2b3..fd0d428d54 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -776,7 +776,7 @@ namespace ts { name: "disableSourceOfProjectReferenceRedirect", type: "boolean", category: Diagnostics.Advanced_Options, - description: Diagnostics.Disable_using_source_of_project_reference_redirect_files + description: Diagnostics.Disable_use_of_source_files_instead_of_declaration_files_from_referenced_projects }, { name: "noImplicitUseStrict", diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 3b4b25986c..fa62a0bb51 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4003,7 +4003,7 @@ "category": "Message", "code": 6220 }, - "Disable using source of project reference redirect files.": { + "Disable use of source files instead of declaration files from referenced projects.": { "category": "Message", "code": 6221 },