From 5c8ef3934d81d77cc9f780419aa8b39128e313c9 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Dec 2018 13:53:51 -0800 Subject: [PATCH] Move the tests into their own folder for easy scenario search --- src/testRunner/tsconfig.json | 83 ++- .../{ => config}/commandLineParsing.ts | 0 .../{ => config}/configurationExtension.ts | 0 .../convertCompilerOptionsFromJson.ts | 0 .../convertTypeAcquisitionFromJson.ts | 0 .../{ => config}/initializeTSConfig.ts | 0 .../unittests/{ => config}/matchFiles.ts | 0 .../{ => config}/projectReferences.ts | 0 .../unittests/{ => config}/showConfig.ts | 0 .../unittests/{ => config}/tsconfigParsing.ts | 0 .../cancellableLanguageServiceOperations.ts | 0 .../{ => services}/convertToAsyncFunction.ts | 0 .../extract/constants.ts} | 0 .../extract/functions.ts} | 0 .../extract/helpers.ts} | 0 .../extract/ranges.ts} | 0 .../{ => services/extract}/symbolWalker.ts | 2 +- .../{ => services}/hostNewLineSupport.ts | 0 .../{ => services}/languageService.ts | 0 .../{ => services}/organizeImports.ts | 0 .../unittests/{ => services}/textChanges.ts | 0 .../{tscWatchEmit.ts => tscWatch/emit.ts} | 0 .../helpers.ts} | 0 .../unittests/tscWatch/resolutionCache.ts | 448 ++++++++++++ .../unittests/{ => tscWatch}/watchApi.ts | 0 .../unittests/tscWatch/watchEnvironment.ts | 178 +++++ .../cachingFileSystemInformation.ts} | 2 +- .../unittests/{ => tsserver}/compileOnSave.ts | 4 +- .../events/largeFileReferenced.ts} | 2 +- .../events/projectLoading.ts} | 2 +- .../events/projectUpdatedInBackground.ts} | 2 +- .../unittests/tsserver/externalProjects.ts | 300 ++++++++ .../helpers.ts} | 0 .../unittests/{ => tsserver}/projectErrors.ts | 0 src/testRunner/unittests/tsserver/reload.ts | 151 ++++ .../{ => tsserver}/resolutionCache.ts | 449 ------------ .../unittests/{ => tsserver}/session.ts | 0 .../unittests/tsserver/skipLibCheck.ts | 229 ++++++ .../symLinks.ts} | 2 +- .../unittests/{ => tsserver}/telemetry.ts | 0 .../unittests/{ => tsserver}/textStorage.ts | 0 .../{ => tsserver}/typingsInstaller.ts | 0 .../unittests/{ => tsserver}/versionCache.ts | 0 .../unittests/tsserver/watchEnvironment.ts | 135 ++++ .../unittests/tsserverProjectSystem.ts | 677 ------------------ src/testRunner/unittests/watchEnvironment.ts | 316 -------- 46 files changed, 1493 insertions(+), 1489 deletions(-) rename src/testRunner/unittests/{ => config}/commandLineParsing.ts (100%) rename src/testRunner/unittests/{ => config}/configurationExtension.ts (100%) rename src/testRunner/unittests/{ => config}/convertCompilerOptionsFromJson.ts (100%) rename src/testRunner/unittests/{ => config}/convertTypeAcquisitionFromJson.ts (100%) rename src/testRunner/unittests/{ => config}/initializeTSConfig.ts (100%) rename src/testRunner/unittests/{ => config}/matchFiles.ts (100%) rename src/testRunner/unittests/{ => config}/projectReferences.ts (100%) rename src/testRunner/unittests/{ => config}/showConfig.ts (100%) rename src/testRunner/unittests/{ => config}/tsconfigParsing.ts (100%) rename src/testRunner/unittests/{ => services}/cancellableLanguageServiceOperations.ts (100%) rename src/testRunner/unittests/{ => services}/convertToAsyncFunction.ts (100%) rename src/testRunner/unittests/{extractConstants.ts => services/extract/constants.ts} (100%) rename src/testRunner/unittests/{extractFunctions.ts => services/extract/functions.ts} (100%) rename src/testRunner/unittests/{extractTestHelpers.ts => services/extract/helpers.ts} (100%) rename src/testRunner/unittests/{extractRanges.ts => services/extract/ranges.ts} (100%) rename src/testRunner/unittests/{ => services/extract}/symbolWalker.ts (97%) rename src/testRunner/unittests/{ => services}/hostNewLineSupport.ts (100%) rename src/testRunner/unittests/{ => services}/languageService.ts (100%) rename src/testRunner/unittests/{ => services}/organizeImports.ts (100%) rename src/testRunner/unittests/{ => services}/textChanges.ts (100%) rename src/testRunner/unittests/{tscWatchEmit.ts => tscWatch/emit.ts} (100%) rename src/testRunner/unittests/{tscWatchHelpers.ts => tscWatch/helpers.ts} (100%) create mode 100644 src/testRunner/unittests/tscWatch/resolutionCache.ts rename src/testRunner/unittests/{ => tscWatch}/watchApi.ts (100%) create mode 100644 src/testRunner/unittests/tscWatch/watchEnvironment.ts rename src/testRunner/unittests/{tsserverCachingFileSystemInformation.ts => tsserver/cachingFileSystemInformation.ts} (98%) rename src/testRunner/unittests/{ => tsserver}/compileOnSave.ts (97%) rename src/testRunner/unittests/{tsserverLargeFileReferencedEvent.ts => tsserver/events/largeFileReferenced.ts} (95%) rename src/testRunner/unittests/{tsserverProjectLoadingEvents.ts => tsserver/events/projectLoading.ts} (96%) rename src/testRunner/unittests/{tsserverProjectUpdatedInBackgroundEvent.ts => tsserver/events/projectUpdatedInBackground.ts} (97%) create mode 100644 src/testRunner/unittests/tsserver/externalProjects.ts rename src/testRunner/unittests/{tsserverHelpers.ts => tsserver/helpers.ts} (100%) rename src/testRunner/unittests/{ => tsserver}/projectErrors.ts (100%) create mode 100644 src/testRunner/unittests/tsserver/reload.ts rename src/testRunner/unittests/{ => tsserver}/resolutionCache.ts (71%) rename src/testRunner/unittests/{ => tsserver}/session.ts (100%) create mode 100644 src/testRunner/unittests/tsserver/skipLibCheck.ts rename src/testRunner/unittests/{tsserverSymLinks.ts => tsserver/symLinks.ts} (97%) rename src/testRunner/unittests/{ => tsserver}/telemetry.ts (100%) rename src/testRunner/unittests/{ => tsserver}/textStorage.ts (100%) rename src/testRunner/unittests/{ => tsserver}/typingsInstaller.ts (100%) rename src/testRunner/unittests/{ => tsserver}/versionCache.ts (100%) create mode 100644 src/testRunner/unittests/tsserver/watchEnvironment.ts delete mode 100644 src/testRunner/unittests/watchEnvironment.ts diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 8a5aab050c7..a7d82b84464 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -36,74 +36,79 @@ "runner.ts", - "unittests/extractTestHelpers.ts", - "unittests/tscWatchHelpers.ts", - "unittests/tsserverHelpers.ts", + "unittests/services/extract/helpers.ts", + "unittests/tscWatch/helpers.ts", + "unittests/tsserver/helpers.ts", "unittests/asserts.ts", "unittests/base64.ts", "unittests/builder.ts", - "unittests/cancellableLanguageServiceOperations.ts", - "unittests/commandLineParsing.ts", - "unittests/compileOnSave.ts", "unittests/compilerCore.ts", - "unittests/configurationExtension.ts", - "unittests/convertCompilerOptionsFromJson.ts", - "unittests/convertToAsyncFunction.ts", "unittests/convertToBase64.ts", - "unittests/convertTypeAcquisitionFromJson.ts", "unittests/customTransforms.ts", - "unittests/extractConstants.ts", - "unittests/extractFunctions.ts", - "unittests/extractRanges.ts", "unittests/factory.ts", - "unittests/hostNewLineSupport.ts", "unittests/incrementalParser.ts", - "unittests/initializeTSConfig.ts", "unittests/jsDocParsing.ts", - "unittests/languageService.ts", - "unittests/matchFiles.ts", "unittests/moduleResolution.ts", - "unittests/organizeImports.ts", "unittests/parsePseudoBigInt.ts", "unittests/paths.ts", "unittests/printer.ts", "unittests/programApi.ts", - "unittests/projectErrors.ts", - "unittests/projectReferences.ts", "unittests/publicApi.ts", - "unittests/resolutionCache.ts", "unittests/reuseProgramStructure.ts", - "unittests/session.ts", "unittests/semver.ts", - "unittests/showConfig.ts", - "unittests/symbolWalker.ts", - "unittests/telemetry.ts", - "unittests/textChanges.ts", - "unittests/textStorage.ts", "unittests/transform.ts", "unittests/transpile.ts", "unittests/tsbuild.ts", "unittests/tsbuildWatchMode.ts", - "unittests/tsconfigParsing.ts", - "unittests/tscWatchEmit.ts", "unittests/tscWatchMode.ts", - "unittests/tsserverCachingFileSystemInformation.ts", - "unittests/tsserverLargeFileReferencedEvent.ts", - "unittests/tsserverProjectLoadingEvents.ts", "unittests/tsserverProjectSystem.ts", - "unittests/tsserverProjectUpdatedInBackgroundEvent.ts", - "unittests/tsserverSymLinks.ts", - "unittests/typingsInstaller.ts", - "unittests/versionCache.ts", - "unittests/watchEnvironment.ts", - "unittests/watchApi.ts", + "unittests/config/commandLineParsing.ts", + "unittests/config/configurationExtension.ts", + "unittests/config/convertCompilerOptionsFromJson.ts", + "unittests/config/convertTypeAcquisitionFromJson.ts", + "unittests/config/initializeTSConfig.ts", + "unittests/config/matchFiles.ts", + "unittests/config/projectReferences.ts", + "unittests/config/showConfig.ts", + "unittests/config/tsconfigParsing.ts", "unittests/evaluation/asyncArrow.ts", "unittests/evaluation/asyncGenerator.ts", "unittests/evaluation/forAwaitOf.ts", + "unittests/services/cancellableLanguageServiceOperations.ts", "unittests/services/colorization.ts", + "unittests/services/convertToAsyncFunction.ts", "unittests/services/documentRegistry.ts", + "unittests/services/extract/constants.ts", + "unittests/services/extract/functions.ts", + "unittests/services/extract/symbolWalker.ts", + "unittests/services/extract/ranges.ts", + "unittests/services/hostNewLineSupport.ts", + "unittests/services/languageService.ts", + "unittests/services/organizeImports.ts", "unittests/services/patternMatcher.ts", - "unittests/services/preProcessFile.ts" + "unittests/services/preProcessFile.ts", + "unittests/services/textChanges.ts", + "unittests/tscWatch/emit.ts", + "unittests/tscWatch/resolutionCache.ts", + "unittests/tscWatch/watchEnvironment.ts", + "unittests/tscWatch/watchApi.ts", + "unittests/tsserver/cachingFileSystemInformation.ts", + "unittests/tsserver/compileOnSave.ts", + "unittests/tsserver/events/largeFileReferenced.ts", + "unittests/tsserver/events/projectLoading.ts", + "unittests/tsserver/events/projectUpdatedInBackground.ts", + "unittests/tsserver/externalProjects.ts", + "unittests/tsserver/projectErrors.ts", + "unittests/tsserver/reload.ts", + "unittests/tsserver/resolutionCache.ts", + "unittests/tsserver/session.ts", + "unittests/tsserver/skipLibCheck.ts", + "unittests/tsserver/symLinks.ts", + "unittests/tsserver/textStorage.ts", + "unittests/tsserver/telemetry.ts", + "unittests/tsserver/typingsInstaller.ts", + "unittests/tsserver/versionCache.ts", + "unittests/tsserver/watchEnvironment.ts" ] } diff --git a/src/testRunner/unittests/commandLineParsing.ts b/src/testRunner/unittests/config/commandLineParsing.ts similarity index 100% rename from src/testRunner/unittests/commandLineParsing.ts rename to src/testRunner/unittests/config/commandLineParsing.ts diff --git a/src/testRunner/unittests/configurationExtension.ts b/src/testRunner/unittests/config/configurationExtension.ts similarity index 100% rename from src/testRunner/unittests/configurationExtension.ts rename to src/testRunner/unittests/config/configurationExtension.ts diff --git a/src/testRunner/unittests/convertCompilerOptionsFromJson.ts b/src/testRunner/unittests/config/convertCompilerOptionsFromJson.ts similarity index 100% rename from src/testRunner/unittests/convertCompilerOptionsFromJson.ts rename to src/testRunner/unittests/config/convertCompilerOptionsFromJson.ts diff --git a/src/testRunner/unittests/convertTypeAcquisitionFromJson.ts b/src/testRunner/unittests/config/convertTypeAcquisitionFromJson.ts similarity index 100% rename from src/testRunner/unittests/convertTypeAcquisitionFromJson.ts rename to src/testRunner/unittests/config/convertTypeAcquisitionFromJson.ts diff --git a/src/testRunner/unittests/initializeTSConfig.ts b/src/testRunner/unittests/config/initializeTSConfig.ts similarity index 100% rename from src/testRunner/unittests/initializeTSConfig.ts rename to src/testRunner/unittests/config/initializeTSConfig.ts diff --git a/src/testRunner/unittests/matchFiles.ts b/src/testRunner/unittests/config/matchFiles.ts similarity index 100% rename from src/testRunner/unittests/matchFiles.ts rename to src/testRunner/unittests/config/matchFiles.ts diff --git a/src/testRunner/unittests/projectReferences.ts b/src/testRunner/unittests/config/projectReferences.ts similarity index 100% rename from src/testRunner/unittests/projectReferences.ts rename to src/testRunner/unittests/config/projectReferences.ts diff --git a/src/testRunner/unittests/showConfig.ts b/src/testRunner/unittests/config/showConfig.ts similarity index 100% rename from src/testRunner/unittests/showConfig.ts rename to src/testRunner/unittests/config/showConfig.ts diff --git a/src/testRunner/unittests/tsconfigParsing.ts b/src/testRunner/unittests/config/tsconfigParsing.ts similarity index 100% rename from src/testRunner/unittests/tsconfigParsing.ts rename to src/testRunner/unittests/config/tsconfigParsing.ts diff --git a/src/testRunner/unittests/cancellableLanguageServiceOperations.ts b/src/testRunner/unittests/services/cancellableLanguageServiceOperations.ts similarity index 100% rename from src/testRunner/unittests/cancellableLanguageServiceOperations.ts rename to src/testRunner/unittests/services/cancellableLanguageServiceOperations.ts diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/services/convertToAsyncFunction.ts similarity index 100% rename from src/testRunner/unittests/convertToAsyncFunction.ts rename to src/testRunner/unittests/services/convertToAsyncFunction.ts diff --git a/src/testRunner/unittests/extractConstants.ts b/src/testRunner/unittests/services/extract/constants.ts similarity index 100% rename from src/testRunner/unittests/extractConstants.ts rename to src/testRunner/unittests/services/extract/constants.ts diff --git a/src/testRunner/unittests/extractFunctions.ts b/src/testRunner/unittests/services/extract/functions.ts similarity index 100% rename from src/testRunner/unittests/extractFunctions.ts rename to src/testRunner/unittests/services/extract/functions.ts diff --git a/src/testRunner/unittests/extractTestHelpers.ts b/src/testRunner/unittests/services/extract/helpers.ts similarity index 100% rename from src/testRunner/unittests/extractTestHelpers.ts rename to src/testRunner/unittests/services/extract/helpers.ts diff --git a/src/testRunner/unittests/extractRanges.ts b/src/testRunner/unittests/services/extract/ranges.ts similarity index 100% rename from src/testRunner/unittests/extractRanges.ts rename to src/testRunner/unittests/services/extract/ranges.ts diff --git a/src/testRunner/unittests/symbolWalker.ts b/src/testRunner/unittests/services/extract/symbolWalker.ts similarity index 97% rename from src/testRunner/unittests/symbolWalker.ts rename to src/testRunner/unittests/services/extract/symbolWalker.ts index 4743b87133b..a027f0f2ce7 100644 --- a/src/testRunner/unittests/symbolWalker.ts +++ b/src/testRunner/unittests/services/extract/symbolWalker.ts @@ -42,4 +42,4 @@ export default function foo(a: number, b: Bar): void {}`, (file, checker) => { assert.equal(stdLibRefSymbols, 1); // Expect 1 stdlib entry symbol - the implicit Array referenced by Bar.history }); }); -} \ No newline at end of file +} diff --git a/src/testRunner/unittests/hostNewLineSupport.ts b/src/testRunner/unittests/services/hostNewLineSupport.ts similarity index 100% rename from src/testRunner/unittests/hostNewLineSupport.ts rename to src/testRunner/unittests/services/hostNewLineSupport.ts diff --git a/src/testRunner/unittests/languageService.ts b/src/testRunner/unittests/services/languageService.ts similarity index 100% rename from src/testRunner/unittests/languageService.ts rename to src/testRunner/unittests/services/languageService.ts diff --git a/src/testRunner/unittests/organizeImports.ts b/src/testRunner/unittests/services/organizeImports.ts similarity index 100% rename from src/testRunner/unittests/organizeImports.ts rename to src/testRunner/unittests/services/organizeImports.ts diff --git a/src/testRunner/unittests/textChanges.ts b/src/testRunner/unittests/services/textChanges.ts similarity index 100% rename from src/testRunner/unittests/textChanges.ts rename to src/testRunner/unittests/services/textChanges.ts diff --git a/src/testRunner/unittests/tscWatchEmit.ts b/src/testRunner/unittests/tscWatch/emit.ts similarity index 100% rename from src/testRunner/unittests/tscWatchEmit.ts rename to src/testRunner/unittests/tscWatch/emit.ts diff --git a/src/testRunner/unittests/tscWatchHelpers.ts b/src/testRunner/unittests/tscWatch/helpers.ts similarity index 100% rename from src/testRunner/unittests/tscWatchHelpers.ts rename to src/testRunner/unittests/tscWatch/helpers.ts diff --git a/src/testRunner/unittests/tscWatch/resolutionCache.ts b/src/testRunner/unittests/tscWatch/resolutionCache.ts new file mode 100644 index 00000000000..759c80e93a6 --- /dev/null +++ b/src/testRunner/unittests/tscWatch/resolutionCache.ts @@ -0,0 +1,448 @@ +namespace ts.tscWatch { + describe("resolutionCache:: tsc-watch module resolution caching", () => { + it("works", () => { + const root = { + path: "/a/d/f0.ts", + content: `import {x} from "f1"` + }; + const imported = { + path: "/a/f1.ts", + content: `foo()` + }; + + const files = [root, imported, libFile]; + const host = createWatchedSystem(files); + const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); + + const f1IsNotModule = getDiagnosticOfFileFromProgram(watch(), root.path, root.content.indexOf('"f1"'), '"f1"'.length, Diagnostics.File_0_is_not_a_module, imported.path); + const cannotFindFoo = getDiagnosticOfFileFromProgram(watch(), imported.path, imported.content.indexOf("foo"), "foo".length, Diagnostics.Cannot_find_name_0, "foo"); + + // ensure that imported file was found + checkOutputErrorsInitial(host, [f1IsNotModule, cannotFindFoo]); + + const originalFileExists = host.fileExists; + { + const newContent = `import {x} from "f1" + var x: string = 1;`; + root.content = newContent; + host.reloadFS(files); + + // patch fileExists to make sure that disk is not touched + host.fileExists = notImplemented; + + // trigger synchronization to make sure that import will be fetched from the cache + host.runQueuedTimeoutCallbacks(); + + // ensure file has correct number of errors after edit + checkOutputErrorsIncremental(host, [ + f1IsNotModule, + getDiagnosticOfFileFromProgram(watch(), root.path, newContent.indexOf("var x") + "var ".length, "x".length, Diagnostics.Type_0_is_not_assignable_to_type_1, 1, "string"), + cannotFindFoo + ]); + } + { + let fileExistsIsCalled = false; + host.fileExists = (fileName): boolean => { + if (fileName === "lib.d.ts") { + return false; + } + fileExistsIsCalled = true; + assert.isTrue(fileName.indexOf("/f2.") !== -1); + return originalFileExists.call(host, fileName); + }; + + root.content = `import {x} from "f2"`; + host.reloadFS(files); + + // trigger synchronization to make sure that LSHost will try to find 'f2' module on disk + host.runQueuedTimeoutCallbacks(); + + // ensure file has correct number of errors after edit + checkOutputErrorsIncremental(host, [ + getDiagnosticModuleNotFoundOfFile(watch(), root, "f2") + ]); + + assert.isTrue(fileExistsIsCalled); + } + { + let fileExistsCalled = false; + host.fileExists = (fileName): boolean => { + if (fileName === "lib.d.ts") { + return false; + } + fileExistsCalled = true; + assert.isTrue(fileName.indexOf("/f1.") !== -1); + return originalFileExists.call(host, fileName); + }; + + const newContent = `import {x} from "f1"`; + root.content = newContent; + + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + + checkOutputErrorsIncremental(host, [f1IsNotModule, cannotFindFoo]); + assert.isTrue(fileExistsCalled); + } + }); + + it("loads missing files from disk", () => { + const root = { + path: `/a/foo.ts`, + content: `import {x} from "bar"` + }; + + const imported = { + path: `/a/bar.d.ts`, + content: `export const y = 1;` + }; + + const files = [root, libFile]; + const host = createWatchedSystem(files); + const originalFileExists = host.fileExists; + + let fileExistsCalledForBar = false; + host.fileExists = fileName => { + if (fileName === "lib.d.ts") { + return false; + } + if (!fileExistsCalledForBar) { + fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1; + } + + return originalFileExists.call(host, fileName); + }; + + const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); + + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); + checkOutputErrorsInitial(host, [ + getDiagnosticModuleNotFoundOfFile(watch(), root, "bar") + ]); + + fileExistsCalledForBar = false; + root.content = `import {y} from "bar"`; + host.reloadFS(files.concat(imported)); + + host.runQueuedTimeoutCallbacks(); + checkOutputErrorsIncremental(host, emptyArray); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); + }); + + it("should compile correctly when resolved module goes missing and then comes back (module is not part of the root)", () => { + const root = { + path: `/a/foo.ts`, + content: `import {x} from "bar"` + }; + + const imported = { + path: `/a/bar.d.ts`, + content: `export const y = 1;export const x = 10;` + }; + + const files = [root, libFile]; + const filesWithImported = files.concat(imported); + const host = createWatchedSystem(filesWithImported); + const originalFileExists = host.fileExists; + let fileExistsCalledForBar = false; + host.fileExists = fileName => { + if (fileName === "lib.d.ts") { + return false; + } + if (!fileExistsCalledForBar) { + fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1; + } + return originalFileExists.call(host, fileName); + }; + + const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); + + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); + checkOutputErrorsInitial(host, emptyArray); + + fileExistsCalledForBar = false; + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); + checkOutputErrorsIncremental(host, [ + getDiagnosticModuleNotFoundOfFile(watch(), root, "bar") + ]); + + fileExistsCalledForBar = false; + host.reloadFS(filesWithImported); + host.checkTimeoutQueueLengthAndRun(1); + checkOutputErrorsIncremental(host, emptyArray); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); + }); + + it("works when module resolution changes to ambient module", () => { + const root = { + path: "/a/b/foo.ts", + content: `import * as fs from "fs";` + }; + + const packageJson = { + path: "/a/b/node_modules/@types/node/package.json", + content: ` +{ + "main": "" +} +` + }; + + const nodeType = { + path: "/a/b/node_modules/@types/node/index.d.ts", + content: ` +declare module "fs" { + export interface Stats { + isFile(): boolean; + } +}` + }; + + const files = [root, libFile]; + const filesWithNodeType = files.concat(packageJson, nodeType); + const host = createWatchedSystem(files, { currentDirectory: "/a/b" }); + + const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { }); + + checkOutputErrorsInitial(host, [ + getDiagnosticModuleNotFoundOfFile(watch(), root, "fs") + ]); + + host.reloadFS(filesWithNodeType); + host.runQueuedTimeoutCallbacks(); + checkOutputErrorsIncremental(host, emptyArray); + }); + + it("works when included file with ambient module changes", () => { + const root = { + path: "/a/b/foo.ts", + content: ` +import * as fs from "fs"; +import * as u from "url"; +` + }; + + const file = { + path: "/a/b/bar.d.ts", + content: ` +declare module "url" { + export interface Url { + href?: string; + } +} +` + }; + + const fileContentWithFS = ` +declare module "fs" { + export interface Stats { + isFile(): boolean; + } +} +`; + + const files = [root, file, libFile]; + const host = createWatchedSystem(files, { currentDirectory: "/a/b" }); + + const watch = createWatchOfFilesAndCompilerOptions([root.path, file.path], host, {}); + + checkOutputErrorsInitial(host, [ + getDiagnosticModuleNotFoundOfFile(watch(), root, "fs") + ]); + + file.content += fileContentWithFS; + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + checkOutputErrorsIncremental(host, emptyArray); + }); + + it("works when reusing program with files from external library", () => { + interface ExpectedFile { path: string; isExpectedToEmit?: boolean; content?: string; } + const configDir = "/a/b/projects/myProject/src/"; + const file1: File = { + path: configDir + "file1.ts", + content: 'import module1 = require("module1");\nmodule1("hello");' + }; + const file2: File = { + path: configDir + "file2.ts", + content: 'import module11 = require("module1");\nmodule11("hello");' + }; + const module1: File = { + path: "/a/b/projects/myProject/node_modules/module1/index.js", + content: "module.exports = options => { return options.toString(); }" + }; + const configFile: File = { + path: configDir + "tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + allowJs: true, + rootDir: ".", + outDir: "../dist", + moduleResolution: "node", + maxNodeModuleJsDepth: 1 + } + }) + }; + const outDirFolder = "/a/b/projects/myProject/dist/"; + const programFiles = [file1, file2, module1, libFile]; + const host = createWatchedSystem(programFiles.concat(configFile), { currentDirectory: "/a/b/projects/myProject/" }); + const watch = createWatchOfConfigFile(configFile.path, host); + checkProgramActualFiles(watch(), programFiles.map(f => f.path)); + checkOutputErrorsInitial(host, emptyArray); + const expectedFiles: ExpectedFile[] = [ + createExpectedEmittedFile(file1), + createExpectedEmittedFile(file2), + createExpectedToNotEmitFile("index.js"), + createExpectedToNotEmitFile("src/index.js"), + createExpectedToNotEmitFile("src/file1.js"), + createExpectedToNotEmitFile("src/file2.js"), + createExpectedToNotEmitFile("lib.js"), + createExpectedToNotEmitFile("lib.d.ts") + ]; + verifyExpectedFiles(expectedFiles); + + file1.content += "\n;"; + expectedFiles[0].content += ";\n"; // Only emit file1 with this change + expectedFiles[1].isExpectedToEmit = false; + host.reloadFS(programFiles.concat(configFile)); + host.runQueuedTimeoutCallbacks(); + checkProgramActualFiles(watch(), programFiles.map(f => f.path)); + checkOutputErrorsIncremental(host, emptyArray); + verifyExpectedFiles(expectedFiles); + + + function verifyExpectedFiles(expectedFiles: ExpectedFile[]) { + forEach(expectedFiles, f => { + assert.equal(!!host.fileExists(f.path), f.isExpectedToEmit, "File " + f.path + " is expected to " + (f.isExpectedToEmit ? "emit" : "not emit")); + if (f.isExpectedToEmit) { + assert.equal(host.readFile(f.path), f.content, "Expected contents of " + f.path); + } + }); + } + + function createExpectedToNotEmitFile(fileName: string): ExpectedFile { + return { + path: outDirFolder + fileName, + isExpectedToEmit: false + }; + } + + function createExpectedEmittedFile(file: File): ExpectedFile { + return { + path: removeFileExtension(file.path.replace(configDir, outDirFolder)) + Extension.Js, + isExpectedToEmit: true, + content: '"use strict";\nexports.__esModule = true;\n' + file.content.replace("import", "var") + "\n" + }; + } + }); + + it("works when renaming node_modules folder that already contains @types folder", () => { + const currentDirectory = "/user/username/projects/myproject"; + const file: File = { + path: `${currentDirectory}/a.ts`, + content: `import * as q from "qqq";` + }; + const module: File = { + path: `${currentDirectory}/node_modules2/@types/qqq/index.d.ts`, + content: "export {}" + }; + const files = [file, module, libFile]; + const host = createWatchedSystem(files, { currentDirectory }); + const watch = createWatchOfFilesAndCompilerOptions([file.path], host); + + checkProgramActualFiles(watch(), [file.path, libFile.path]); + checkOutputErrorsInitial(host, [getDiagnosticModuleNotFoundOfFile(watch(), file, "qqq")]); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectories(host, [`${currentDirectory}/node_modules`, `${currentDirectory}/node_modules/@types`], /*recursive*/ true); + + host.renameFolder(`${currentDirectory}/node_modules2`, `${currentDirectory}/node_modules`); + host.runQueuedTimeoutCallbacks(); + checkProgramActualFiles(watch(), [file.path, libFile.path, `${currentDirectory}/node_modules/@types/qqq/index.d.ts`]); + checkOutputErrorsIncremental(host, emptyArray); + }); + + describe("ignores files/folder changes in node_modules that start with '.'", () => { + const projectPath = "/user/username/projects/project"; + const npmCacheFile: File = { + path: `${projectPath}/node_modules/.cache/babel-loader/89c02171edab901b9926470ba6d5677e.ts`, + content: JSON.stringify({ something: 10 }) + }; + const file1: File = { + path: `${projectPath}/test.ts`, + content: `import { x } from "somemodule";` + }; + const file2: File = { + path: `${projectPath}/node_modules/somemodule/index.d.ts`, + content: `export const x = 10;` + }; + const files = [libFile, file1, file2]; + const expectedFiles = files.map(f => f.path); + it("when watching node_modules in inferred project for failed lookup", () => { + const host = createWatchedSystem(files); + const watch = createWatchOfFilesAndCompilerOptions([file1.path], host, {}, /*maxNumberOfFilesToIterateForInvalidation*/ 1); + checkProgramActualFiles(watch(), expectedFiles); + host.checkTimeoutQueueLength(0); + + host.ensureFileOrFolder(npmCacheFile); + host.checkTimeoutQueueLength(0); + }); + it("when watching node_modules as part of wild card directories in config project", () => { + const config: File = { + path: `${projectPath}/tsconfig.json`, + content: "{}" + }; + const host = createWatchedSystem(files.concat(config)); + const watch = createWatchOfConfigFile(config.path, host); + checkProgramActualFiles(watch(), expectedFiles); + host.checkTimeoutQueueLength(0); + + host.ensureFileOrFolder(npmCacheFile); + host.checkTimeoutQueueLength(0); + }); + }); + }); + + describe("resolutionCache:: tsc-watch with modules linked to sibling folder", () => { + const projectRoot = "/user/username/projects/project"; + const mainPackageRoot = `${projectRoot}/main`; + const linkedPackageRoot = `${projectRoot}/linked-package`; + const mainFile: File = { + path: `${mainPackageRoot}/index.ts`, + content: "import { Foo } from '@scoped/linked-package'" + }; + const config: File = { + path: `${mainPackageRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { module: "commonjs", moduleResolution: "node", baseUrl: ".", rootDir: "." }, + files: ["index.ts"] + }) + }; + const linkedPackageInMain: SymLink = { + path: `${mainPackageRoot}/node_modules/@scoped/linked-package`, + symLink: `${linkedPackageRoot}` + }; + const linkedPackageJson: File = { + path: `${linkedPackageRoot}/package.json`, + content: JSON.stringify({ name: "@scoped/linked-package", version: "0.0.1", types: "dist/index.d.ts", main: "dist/index.js" }) + }; + const linkedPackageIndex: File = { + path: `${linkedPackageRoot}/dist/index.d.ts`, + content: "export * from './other';" + }; + const linkedPackageOther: File = { + path: `${linkedPackageRoot}/dist/other.d.ts`, + content: 'export declare const Foo = "BAR";' + }; + + it("verify watched directories", () => { + const files = [libFile, mainFile, config, linkedPackageInMain, linkedPackageJson, linkedPackageIndex, linkedPackageOther]; + const host = createWatchedSystem(files, { currentDirectory: mainPackageRoot }); + createWatchOfConfigFile("tsconfig.json", host); + checkWatchedFilesDetailed(host, [libFile.path, mainFile.path, config.path, linkedPackageIndex.path, linkedPackageOther.path], 1); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectoriesDetailed(host, [`${mainPackageRoot}/@scoped`, `${mainPackageRoot}/node_modules`, linkedPackageRoot, `${mainPackageRoot}/node_modules/@types`, `${projectRoot}/node_modules/@types`], 1, /*recursive*/ true); + }); + }); +} diff --git a/src/testRunner/unittests/watchApi.ts b/src/testRunner/unittests/tscWatch/watchApi.ts similarity index 100% rename from src/testRunner/unittests/watchApi.ts rename to src/testRunner/unittests/tscWatch/watchApi.ts diff --git a/src/testRunner/unittests/tscWatch/watchEnvironment.ts b/src/testRunner/unittests/tscWatch/watchEnvironment.ts new file mode 100644 index 00000000000..646fb59ef09 --- /dev/null +++ b/src/testRunner/unittests/tscWatch/watchEnvironment.ts @@ -0,0 +1,178 @@ +namespace ts.tscWatch { + import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory; + describe("watchEnvironment:: tsc-watch with different polling/non polling options", () => { + it("watchFile using dynamic priority polling", () => { + const projectFolder = "/a/username/project"; + const file1: File = { + path: `${projectFolder}/typescript.ts`, + content: "var z = 10;" + }; + const files = [file1, libFile]; + const environmentVariables = createMap(); + environmentVariables.set("TSC_WATCHFILE", "DynamicPriorityPolling"); + const host = createWatchedSystem(files, { environmentVariables }); + const watch = createWatchOfFilesAndCompilerOptions([file1.path], host); + + const initialProgram = watch(); + verifyProgram(); + + const mediumPollingIntervalThreshold = unchangedPollThresholds[PollingInterval.Medium]; + for (let index = 0; index < mediumPollingIntervalThreshold; index++) { + // Transition libFile and file1 to low priority queue + host.checkTimeoutQueueLengthAndRun(1); + assert.deepEqual(watch(), initialProgram); + } + + // Make a change to file + file1.content = "var zz30 = 100;"; + host.reloadFS(files); + + // This should detect change in the file + host.checkTimeoutQueueLengthAndRun(1); + assert.deepEqual(watch(), initialProgram); + + // Callbacks: medium priority + high priority queue and scheduled program update + host.checkTimeoutQueueLengthAndRun(3); + // During this timeout the file would be detected as unchanged + let fileUnchangeDetected = 1; + const newProgram = watch(); + assert.notStrictEqual(newProgram, initialProgram); + + verifyProgram(); + const outputFile1 = changeExtension(file1.path, ".js"); + assert.isTrue(host.fileExists(outputFile1)); + assert.equal(host.readFile(outputFile1), file1.content + host.newLine); + + const newThreshold = unchangedPollThresholds[PollingInterval.Low] + mediumPollingIntervalThreshold; + for (; fileUnchangeDetected < newThreshold; fileUnchangeDetected++) { + // For high + Medium/low polling interval + host.checkTimeoutQueueLengthAndRun(2); + assert.deepEqual(watch(), newProgram); + } + + // Everything goes in high polling interval queue + host.checkTimeoutQueueLengthAndRun(1); + assert.deepEqual(watch(), newProgram); + + function verifyProgram() { + checkProgramActualFiles(watch(), files.map(f => f.path)); + checkWatchedFiles(host, []); + checkWatchedDirectories(host, [], /*recursive*/ false); + checkWatchedDirectories(host, [], /*recursive*/ true); + } + }); + + describe("tsc-watch when watchDirectories implementation", () => { + function verifyRenamingFileInSubFolder(tscWatchDirectory: Tsc_WatchDirectory) { + const projectFolder = "/a/username/project"; + const projectSrcFolder = `${projectFolder}/src`; + const configFile: File = { + path: `${projectFolder}/tsconfig.json`, + content: "{}" + }; + const file: File = { + path: `${projectSrcFolder}/file1.ts`, + content: "" + }; + const programFiles = [file, libFile]; + const files = [file, configFile, libFile]; + const environmentVariables = createMap(); + environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory); + const host = createWatchedSystem(files, { environmentVariables }); + const watch = createWatchOfConfigFile(configFile.path, host); + const projectFolders = [projectFolder, projectSrcFolder, `${projectFolder}/node_modules/@types`]; + // Watching files config file, file, lib file + const expectedWatchedFiles = files.map(f => f.path); + const expectedWatchedDirectories = tscWatchDirectory === Tsc_WatchDirectory.NonRecursiveWatchDirectory ? projectFolders : emptyArray; + if (tscWatchDirectory === Tsc_WatchDirectory.WatchFile) { + expectedWatchedFiles.push(...projectFolders); + } + + verifyProgram(checkOutputErrorsInitial); + + // Rename the file: + file.path = file.path.replace("file1.ts", "file2.ts"); + expectedWatchedFiles[0] = file.path; + host.reloadFS(files); + if (tscWatchDirectory === Tsc_WatchDirectory.DynamicPolling) { + // With dynamic polling the fs change would be detected only by running timeouts + host.runQueuedTimeoutCallbacks(); + } + // Delayed update program + host.runQueuedTimeoutCallbacks(); + verifyProgram(checkOutputErrorsIncremental); + + function verifyProgram(checkOutputErrors: (host: WatchedSystem, errors: ReadonlyArray) => void) { + checkProgramActualFiles(watch(), programFiles.map(f => f.path)); + checkOutputErrors(host, emptyArray); + + const outputFile = changeExtension(file.path, ".js"); + assert(host.fileExists(outputFile)); + assert.equal(host.readFile(outputFile), file.content); + + checkWatchedDirectories(host, emptyArray, /*recursive*/ true); + + // Watching config file, file, lib file and directories + checkWatchedFilesDetailed(host, expectedWatchedFiles, 1); + checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, 1, /*recursive*/ false); + } + } + + it("uses watchFile when renaming file in subfolder", () => { + verifyRenamingFileInSubFolder(Tsc_WatchDirectory.WatchFile); + }); + + it("uses non recursive watchDirectory when renaming file in subfolder", () => { + verifyRenamingFileInSubFolder(Tsc_WatchDirectory.NonRecursiveWatchDirectory); + }); + + it("uses non recursive dynamic polling when renaming file in subfolder", () => { + verifyRenamingFileInSubFolder(Tsc_WatchDirectory.DynamicPolling); + }); + + it("when there are symlinks to folders in recursive folders", () => { + const cwd = "/home/user/projects/myproject"; + const file1: File = { + path: `${cwd}/src/file.ts`, + content: `import * as a from "a"` + }; + const tsconfig: File = { + path: `${cwd}/tsconfig.json`, + content: `{ "compilerOptions": { "extendedDiagnostics": true, "traceResolution": true }}` + }; + const realA: File = { + path: `${cwd}/node_modules/reala/index.d.ts`, + content: `export {}` + }; + const realB: File = { + path: `${cwd}/node_modules/realb/index.d.ts`, + content: `export {}` + }; + const symLinkA: SymLink = { + path: `${cwd}/node_modules/a`, + symLink: `${cwd}/node_modules/reala` + }; + const symLinkB: SymLink = { + path: `${cwd}/node_modules/b`, + symLink: `${cwd}/node_modules/realb` + }; + const symLinkBInA: SymLink = { + path: `${cwd}/node_modules/reala/node_modules/b`, + symLink: `${cwd}/node_modules/b` + }; + const symLinkAInB: SymLink = { + path: `${cwd}/node_modules/realb/node_modules/a`, + symLink: `${cwd}/node_modules/a` + }; + const files = [file1, tsconfig, realA, realB, symLinkA, symLinkB, symLinkBInA, symLinkAInB]; + const environmentVariables = createMap(); + environmentVariables.set("TSC_WATCHDIRECTORY", Tsc_WatchDirectory.NonRecursiveWatchDirectory); + const host = createWatchedSystem(files, { environmentVariables, currentDirectory: cwd }); + createWatchOfConfigFile("tsconfig.json", host); + checkWatchedDirectories(host, emptyArray, /*recursive*/ true); + checkWatchedDirectories(host, [cwd, `${cwd}/node_modules`, `${cwd}/node_modules/@types`, `${cwd}/node_modules/reala`, `${cwd}/node_modules/realb`, + `${cwd}/node_modules/reala/node_modules`, `${cwd}/node_modules/realb/node_modules`, `${cwd}/src`], /*recursive*/ false); + }); + }); + }); +} diff --git a/src/testRunner/unittests/tsserverCachingFileSystemInformation.ts b/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts similarity index 98% rename from src/testRunner/unittests/tsserverCachingFileSystemInformation.ts rename to src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts index 65e1ec564d3..b5132f57c0e 100644 --- a/src/testRunner/unittests/tsserverCachingFileSystemInformation.ts +++ b/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts @@ -3,7 +3,7 @@ namespace ts.projectSystem { return countWhere(recursiveWatchedDirs, dir => file.length > dir.length && startsWith(file, dir) && file[dir.length] === directorySeparator); } - describe("tsserverCachingFileSystemInformation:: tsserverProjectSystem CachingFileSystemInformation", () => { + describe("tsserver:: CachingFileSystemInformation:: tsserverProjectSystem CachingFileSystemInformation", () => { enum CalledMapsWithSingleArg { fileExists = "fileExists", directoryExists = "directoryExists", diff --git a/src/testRunner/unittests/compileOnSave.ts b/src/testRunner/unittests/tsserver/compileOnSave.ts similarity index 97% rename from src/testRunner/unittests/compileOnSave.ts rename to src/testRunner/unittests/tsserver/compileOnSave.ts index 2352ac58c19..310a6e9e33c 100644 --- a/src/testRunner/unittests/compileOnSave.ts +++ b/src/testRunner/unittests/tsserver/compileOnSave.ts @@ -6,7 +6,7 @@ namespace ts.projectSystem { return new TestTypingsInstaller("/a/data/", /*throttleLimit*/5, host); } - describe("compileOnSave:: affected list", () => { + describe("tsserver:: compileOnSave:: affected list", () => { function sendAffectedFileRequestAndCheckResult(session: server.Session, request: server.protocol.Request, expectedFileList: { projectFileName: string, files: File[] }[]) { const response = session.executeCommand(request).response as server.protocol.CompileOnSaveAffectedFileListSingleProject[]; const actualResult = response.sort((list1, list2) => compareStringsCaseSensitive(list1.projectFileName, list2.projectFileName)); @@ -552,7 +552,7 @@ namespace ts.projectSystem { }); }); - describe("compileOnSave:: EmitFile test", () => { + describe("tsserver:: compileOnSave:: EmitFile test", () => { it("should respect line endings", () => { test("\n"); test("\r\n"); diff --git a/src/testRunner/unittests/tsserverLargeFileReferencedEvent.ts b/src/testRunner/unittests/tsserver/events/largeFileReferenced.ts similarity index 95% rename from src/testRunner/unittests/tsserverLargeFileReferencedEvent.ts rename to src/testRunner/unittests/tsserver/events/largeFileReferenced.ts index f33f32dfe58..ca7f839e1b0 100644 --- a/src/testRunner/unittests/tsserverLargeFileReferencedEvent.ts +++ b/src/testRunner/unittests/tsserver/events/largeFileReferenced.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserverLargeFileReferencedEvent:: LargeFileReferencedEvent with large file", () => { + describe("tsserver:: LargeFileReferencedEvent with large file", () => { const projectRoot = "/user/username/projects/project"; function getLargeFile(useLargeTsFile: boolean) { diff --git a/src/testRunner/unittests/tsserverProjectLoadingEvents.ts b/src/testRunner/unittests/tsserver/events/projectLoading.ts similarity index 96% rename from src/testRunner/unittests/tsserverProjectLoadingEvents.ts rename to src/testRunner/unittests/tsserver/events/projectLoading.ts index 0ff256bd692..2de3ab40db6 100644 --- a/src/testRunner/unittests/tsserverProjectLoadingEvents.ts +++ b/src/testRunner/unittests/tsserver/events/projectLoading.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserverProjectLoadingEvents:: ProjectLoadingStart and ProjectLoadingFinish events", () => { + describe("tsserver:: events:: ProjectLoadingStart and ProjectLoadingFinish events", () => { const projectRoot = "/user/username/projects"; const aTs: File = { path: `${projectRoot}/a/a.ts`, diff --git a/src/testRunner/unittests/tsserverProjectUpdatedInBackgroundEvent.ts b/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts similarity index 97% rename from src/testRunner/unittests/tsserverProjectUpdatedInBackgroundEvent.ts rename to src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts index 3e8382fff58..c818f4d2a8d 100644 --- a/src/testRunner/unittests/tsserverProjectUpdatedInBackgroundEvent.ts +++ b/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserverProjectUpdatedInBackgroundEvent:: ProjectsUpdatedInBackground", () => { + describe("tsserver:: ProjectsUpdatedInBackground", () => { function verifyFiles(caption: string, actual: ReadonlyArray, expected: ReadonlyArray) { assert.equal(actual.length, expected.length, `Incorrect number of ${caption}. Actual: ${actual} Expected: ${expected}`); const seen = createMap(); diff --git a/src/testRunner/unittests/tsserver/externalProjects.ts b/src/testRunner/unittests/tsserver/externalProjects.ts new file mode 100644 index 00000000000..6c246a2e3bd --- /dev/null +++ b/src/testRunner/unittests/tsserver/externalProjects.ts @@ -0,0 +1,300 @@ +namespace ts.projectSystem { + describe("tsserver:: ExternalProjects", () => { + describe("correctly handling add/remove tsconfig - 1", () => { + function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1;" + }; + const f2 = { + path: "/a/b/lib.ts", + content: "" + }; + const tsconfig = { + path: "/a/b/tsconfig.json", + content: "" + }; + const host = createServerHost([f1, f2]); + const projectService = createProjectService(host); + projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); + + // open external project + const projectName = "/a/b/proj1"; + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path, f2.path]), + options: {} + }); + projectService.openClientFile(f1.path); + projectService.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]); + + // rename lib.ts to tsconfig.json + host.reloadFS([f1, tsconfig]); + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path, tsconfig.path]), + options: {} + }); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + if (lazyConfiguredProjectsFromExternalProject) { + checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed + projectService.ensureInferredProjectsUpToDate_TestOnly(); + } + checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, tsconfig.path]); + + // rename tsconfig.json back to lib.ts + host.reloadFS([f1, f2]); + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path, f2.path]), + options: {} + }); + + projectService.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]); + } + it("when lazyConfiguredProjectsFromExternalProject not set", () => { + verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false); + }); + it("when lazyConfiguredProjectsFromExternalProject is set", () => { + verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ true); + }); + }); + + describe("correctly handling add/remove tsconfig - 2", () => { + function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1;" + }; + const cLib = { + path: "/a/b/c/lib.ts", + content: "" + }; + const cTsconfig = { + path: "/a/b/c/tsconfig.json", + content: "{}" + }; + const dLib = { + path: "/a/b/d/lib.ts", + content: "" + }; + const dTsconfig = { + path: "/a/b/d/tsconfig.json", + content: "{}" + }; + const host = createServerHost([f1, cLib, cTsconfig, dLib, dTsconfig]); + const projectService = createProjectService(host); + projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); + + // open external project + const projectName = "/a/b/proj1"; + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path]), + options: {} + }); + + projectService.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(projectService.externalProjects[0], [f1.path]); + + // add two config file as root files + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]), + options: {} + }); + projectService.checkNumberOfProjects({ configuredProjects: 2 }); + if (lazyConfiguredProjectsFromExternalProject) { + checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed + checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed + projectService.ensureInferredProjectsUpToDate_TestOnly(); + } + checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]); + + // remove one config file + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path, dTsconfig.path]), + options: {} + }); + + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [dLib.path, dTsconfig.path]); + + // remove second config file + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path]), + options: {} + }); + + projectService.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(projectService.externalProjects[0], [f1.path]); + + // open two config files + // add two config file as root files + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]), + options: {} + }); + projectService.checkNumberOfProjects({ configuredProjects: 2 }); + if (lazyConfiguredProjectsFromExternalProject) { + checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed + checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed + projectService.ensureInferredProjectsUpToDate_TestOnly(); + } + checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]); + + // close all projects - no projects should be opened + projectService.closeExternalProject(projectName); + projectService.checkNumberOfProjects({}); + } + + it("when lazyConfiguredProjectsFromExternalProject not set", () => { + verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false); + }); + it("when lazyConfiguredProjectsFromExternalProject is set", () => { + verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ true); + }); + }); + + it("correctly handles changes in lib section of config file", () => { + const libES5 = { + path: "/compiler/lib.es5.d.ts", + content: "declare const eval: any" + }; + const libES2015Promise = { + path: "/compiler/lib.es2015.promise.d.ts", + content: "declare class Promise {}" + }; + const app = { + path: "/src/app.ts", + content: "var x: Promise;" + }; + const config1 = { + path: "/src/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: true, + sourceMap: false, + lib: [ + "es5" + ] + } + }) + }; + const config2 = { + path: config1.path, + content: JSON.stringify( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: true, + sourceMap: false, + lib: [ + "es5", + "es2015.promise" + ] + } + }) + }; + const host = createServerHost([libES5, libES2015Promise, app, config1], { executingFilePath: "/compiler/tsc.js" }); + const projectService = createProjectService(host); + projectService.openClientFile(app.path); + + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [libES5.path, app.path, config1.path]); + + host.reloadFS([libES5, libES2015Promise, app, config2]); + host.checkTimeoutQueueLengthAndRun(2); + + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [libES5.path, libES2015Promise.path, app.path, config2.path]); + }); + + it("should handle non-existing directories in config file", () => { + const f = { + path: "/a/src/app.ts", + content: "let x = 1;" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: {}, + include: [ + "src/**/*", + "notexistingfolder/*" + ] + }) + }; + const host = createServerHost([f, config]); + const projectService = createProjectService(host); + projectService.openClientFile(f.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + const project = projectService.configuredProjects.get(config.path)!; + assert.isTrue(project.hasOpenRef()); // f + + projectService.closeClientFile(f.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config.path), project); + assert.isFalse(project.hasOpenRef()); // No files + assert.isFalse(project.isClosed()); + + projectService.openClientFile(f.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config.path), project); + assert.isTrue(project.hasOpenRef()); // f + assert.isFalse(project.isClosed()); + }); + + it("handles loads existing configured projects of external projects when lazyConfiguredProjectsFromExternalProject is disabled", () => { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({}) + }; + const projectFileName = "/a/b/project.csproj"; + const host = createServerHost([f1, config]); + const service = createProjectService(host); + service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: true } }); + service.openExternalProject({ + projectFileName, + rootFiles: toExternalFiles([f1.path, config.path]), + options: {} + }); + service.checkNumberOfProjects({ configuredProjects: 1 }); + const project = service.configuredProjects.get(config.path)!; + assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded + checkProjectActualFiles(project, emptyArray); + + service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: false } }); + assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded + checkProjectActualFiles(project, [config.path, f1.path]); + + service.closeExternalProject(projectFileName); + service.checkNumberOfProjects({}); + + service.openExternalProject({ + projectFileName, + rootFiles: toExternalFiles([f1.path, config.path]), + options: {} + }); + service.checkNumberOfProjects({ configuredProjects: 1 }); + const project2 = service.configuredProjects.get(config.path)!; + assert.equal(project2.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded + checkProjectActualFiles(project2, [config.path, f1.path]); + }); + }); +} diff --git a/src/testRunner/unittests/tsserverHelpers.ts b/src/testRunner/unittests/tsserver/helpers.ts similarity index 100% rename from src/testRunner/unittests/tsserverHelpers.ts rename to src/testRunner/unittests/tsserver/helpers.ts diff --git a/src/testRunner/unittests/projectErrors.ts b/src/testRunner/unittests/tsserver/projectErrors.ts similarity index 100% rename from src/testRunner/unittests/projectErrors.ts rename to src/testRunner/unittests/tsserver/projectErrors.ts diff --git a/src/testRunner/unittests/tsserver/reload.ts b/src/testRunner/unittests/tsserver/reload.ts new file mode 100644 index 00000000000..80f3e56e437 --- /dev/null +++ b/src/testRunner/unittests/tsserver/reload.ts @@ -0,0 +1,151 @@ +namespace ts.projectSystem { + describe("tsserver:: reload", () => { + it("should work with temp file", () => { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const tmp = { + path: "/a/b/app.tmp", + content: "const y = 42" + }; + const host = createServerHost([f1, tmp]); + const session = createSession(host); + + // send open request + session.executeCommand({ + type: "request", + command: "open", + seq: 1, + arguments: { file: f1.path } + }); + + // reload from tmp file + session.executeCommand({ + type: "request", + command: "reload", + seq: 2, + arguments: { file: f1.path, tmpfile: tmp.path } + }); + + // verify content + const projectServiice = session.getProjectService(); + const snap1 = projectServiice.getScriptInfo(f1.path)!.getSnapshot(); + assert.equal(getSnapshotText(snap1), tmp.content, "content should be equal to the content of temp file"); + + // reload from original file file + session.executeCommand({ + type: "request", + command: "reload", + seq: 2, + arguments: { file: f1.path } + }); + + // verify content + const snap2 = projectServiice.getScriptInfo(f1.path)!.getSnapshot(); + assert.equal(getSnapshotText(snap2), f1.content, "content should be equal to the content of original file"); + + }); + + it("should work when script info doesnt have any project open", () => { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const tmp = { + path: "/a/b/app.tmp", + content: "const y = 42" + }; + const host = createServerHost([f1, tmp, libFile]); + const session = createSession(host); + const openContent = "let z = 1"; + // send open request + session.executeCommandSeq({ + command: server.protocol.CommandTypes.Open, + arguments: { file: f1.path, fileContent: openContent } + }); + + const projectService = session.getProjectService(); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const info = projectService.getScriptInfo(f1.path)!; + assert.isDefined(info); + checkScriptInfoContents(openContent, "contents set during open request"); + + // send close request + session.executeCommandSeq({ + command: server.protocol.CommandTypes.Close, + arguments: { file: f1.path } + }); + checkScriptInfoAndProjects(f1.content, "contents of closed file"); + checkInferredProjectIsOrphan(); + + // Can reload contents of the file when its not open and has no project + // reload from temp file + session.executeCommandSeq({ + command: server.protocol.CommandTypes.Reload, + arguments: { file: f1.path, tmpfile: tmp.path } + }); + checkScriptInfoAndProjects(tmp.content, "contents of temp file"); + checkInferredProjectIsOrphan(); + + // reload from own file + session.executeCommandSeq({ + command: server.protocol.CommandTypes.Reload, + arguments: { file: f1.path } + }); + checkScriptInfoAndProjects(f1.content, "contents of closed file"); + checkInferredProjectIsOrphan(); + + // Open file again without setting its content + session.executeCommandSeq({ + command: server.protocol.CommandTypes.Open, + arguments: { file: f1.path } + }); + checkScriptInfoAndProjects(f1.content, "contents of file when opened without specifying contents"); + const snap = info.getSnapshot(); + + // send close request + session.executeCommandSeq({ + command: server.protocol.CommandTypes.Close, + arguments: { file: f1.path } + }); + checkScriptInfoAndProjects(f1.content, "contents of closed file"); + assert.strictEqual(info.getSnapshot(), snap); + checkInferredProjectIsOrphan(); + + // reload from temp file + session.executeCommandSeq({ + command: server.protocol.CommandTypes.Reload, + arguments: { file: f1.path, tmpfile: tmp.path } + }); + checkScriptInfoAndProjects(tmp.content, "contents of temp file"); + assert.notStrictEqual(info.getSnapshot(), snap); + checkInferredProjectIsOrphan(); + + // reload from own file + session.executeCommandSeq({ + command: server.protocol.CommandTypes.Reload, + arguments: { file: f1.path } + }); + checkScriptInfoAndProjects(f1.content, "contents of closed file"); + assert.notStrictEqual(info.getSnapshot(), snap); + checkInferredProjectIsOrphan(); + + function checkInferredProjectIsOrphan() { + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + assert.equal(info.containingProjects.length, 0); + } + + function checkScriptInfoAndProjects(contentsOfInfo: string, captionForContents: string) { + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + assert.strictEqual(projectService.getScriptInfo(f1.path), info); + checkScriptInfoContents(contentsOfInfo, captionForContents); + } + + function checkScriptInfoContents(contentsOfInfo: string, captionForContents: string) { + const snap = info.getSnapshot(); + assert.equal(getSnapshotText(snap), contentsOfInfo, "content should be equal to " + captionForContents); + } + }); + }); +} diff --git a/src/testRunner/unittests/resolutionCache.ts b/src/testRunner/unittests/tsserver/resolutionCache.ts similarity index 71% rename from src/testRunner/unittests/resolutionCache.ts rename to src/testRunner/unittests/tsserver/resolutionCache.ts index d97770e21b5..a07cb7721d4 100644 --- a/src/testRunner/unittests/resolutionCache.ts +++ b/src/testRunner/unittests/tsserver/resolutionCache.ts @@ -1,452 +1,3 @@ -namespace ts.tscWatch { - describe("resolutionCache:: tsc-watch module resolution caching", () => { - it("works", () => { - const root = { - path: "/a/d/f0.ts", - content: `import {x} from "f1"` - }; - const imported = { - path: "/a/f1.ts", - content: `foo()` - }; - - const files = [root, imported, libFile]; - const host = createWatchedSystem(files); - const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); - - const f1IsNotModule = getDiagnosticOfFileFromProgram(watch(), root.path, root.content.indexOf('"f1"'), '"f1"'.length, Diagnostics.File_0_is_not_a_module, imported.path); - const cannotFindFoo = getDiagnosticOfFileFromProgram(watch(), imported.path, imported.content.indexOf("foo"), "foo".length, Diagnostics.Cannot_find_name_0, "foo"); - - // ensure that imported file was found - checkOutputErrorsInitial(host, [f1IsNotModule, cannotFindFoo]); - - const originalFileExists = host.fileExists; - { - const newContent = `import {x} from "f1" - var x: string = 1;`; - root.content = newContent; - host.reloadFS(files); - - // patch fileExists to make sure that disk is not touched - host.fileExists = notImplemented; - - // trigger synchronization to make sure that import will be fetched from the cache - host.runQueuedTimeoutCallbacks(); - - // ensure file has correct number of errors after edit - checkOutputErrorsIncremental(host, [ - f1IsNotModule, - getDiagnosticOfFileFromProgram(watch(), root.path, newContent.indexOf("var x") + "var ".length, "x".length, Diagnostics.Type_0_is_not_assignable_to_type_1, 1, "string"), - cannotFindFoo - ]); - } - { - let fileExistsIsCalled = false; - host.fileExists = (fileName): boolean => { - if (fileName === "lib.d.ts") { - return false; - } - fileExistsIsCalled = true; - assert.isTrue(fileName.indexOf("/f2.") !== -1); - return originalFileExists.call(host, fileName); - }; - - root.content = `import {x} from "f2"`; - host.reloadFS(files); - - // trigger synchronization to make sure that LSHost will try to find 'f2' module on disk - host.runQueuedTimeoutCallbacks(); - - // ensure file has correct number of errors after edit - checkOutputErrorsIncremental(host, [ - getDiagnosticModuleNotFoundOfFile(watch(), root, "f2") - ]); - - assert.isTrue(fileExistsIsCalled); - } - { - let fileExistsCalled = false; - host.fileExists = (fileName): boolean => { - if (fileName === "lib.d.ts") { - return false; - } - fileExistsCalled = true; - assert.isTrue(fileName.indexOf("/f1.") !== -1); - return originalFileExists.call(host, fileName); - }; - - const newContent = `import {x} from "f1"`; - root.content = newContent; - - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - - checkOutputErrorsIncremental(host, [f1IsNotModule, cannotFindFoo]); - assert.isTrue(fileExistsCalled); - } - }); - - it("loads missing files from disk", () => { - const root = { - path: `/a/foo.ts`, - content: `import {x} from "bar"` - }; - - const imported = { - path: `/a/bar.d.ts`, - content: `export const y = 1;` - }; - - const files = [root, libFile]; - const host = createWatchedSystem(files); - const originalFileExists = host.fileExists; - - let fileExistsCalledForBar = false; - host.fileExists = fileName => { - if (fileName === "lib.d.ts") { - return false; - } - if (!fileExistsCalledForBar) { - fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1; - } - - return originalFileExists.call(host, fileName); - }; - - const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); - - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); - checkOutputErrorsInitial(host, [ - getDiagnosticModuleNotFoundOfFile(watch(), root, "bar") - ]); - - fileExistsCalledForBar = false; - root.content = `import {y} from "bar"`; - host.reloadFS(files.concat(imported)); - - host.runQueuedTimeoutCallbacks(); - checkOutputErrorsIncremental(host, emptyArray); - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); - }); - - it("should compile correctly when resolved module goes missing and then comes back (module is not part of the root)", () => { - const root = { - path: `/a/foo.ts`, - content: `import {x} from "bar"` - }; - - const imported = { - path: `/a/bar.d.ts`, - content: `export const y = 1;export const x = 10;` - }; - - const files = [root, libFile]; - const filesWithImported = files.concat(imported); - const host = createWatchedSystem(filesWithImported); - const originalFileExists = host.fileExists; - let fileExistsCalledForBar = false; - host.fileExists = fileName => { - if (fileName === "lib.d.ts") { - return false; - } - if (!fileExistsCalledForBar) { - fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1; - } - return originalFileExists.call(host, fileName); - }; - - const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); - - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); - checkOutputErrorsInitial(host, emptyArray); - - fileExistsCalledForBar = false; - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); - checkOutputErrorsIncremental(host, [ - getDiagnosticModuleNotFoundOfFile(watch(), root, "bar") - ]); - - fileExistsCalledForBar = false; - host.reloadFS(filesWithImported); - host.checkTimeoutQueueLengthAndRun(1); - checkOutputErrorsIncremental(host, emptyArray); - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); - }); - - it("works when module resolution changes to ambient module", () => { - const root = { - path: "/a/b/foo.ts", - content: `import * as fs from "fs";` - }; - - const packageJson = { - path: "/a/b/node_modules/@types/node/package.json", - content: ` -{ - "main": "" -} -` - }; - - const nodeType = { - path: "/a/b/node_modules/@types/node/index.d.ts", - content: ` -declare module "fs" { - export interface Stats { - isFile(): boolean; - } -}` - }; - - const files = [root, libFile]; - const filesWithNodeType = files.concat(packageJson, nodeType); - const host = createWatchedSystem(files, { currentDirectory: "/a/b" }); - - const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { }); - - checkOutputErrorsInitial(host, [ - getDiagnosticModuleNotFoundOfFile(watch(), root, "fs") - ]); - - host.reloadFS(filesWithNodeType); - host.runQueuedTimeoutCallbacks(); - checkOutputErrorsIncremental(host, emptyArray); - }); - - it("works when included file with ambient module changes", () => { - const root = { - path: "/a/b/foo.ts", - content: ` -import * as fs from "fs"; -import * as u from "url"; -` - }; - - const file = { - path: "/a/b/bar.d.ts", - content: ` -declare module "url" { - export interface Url { - href?: string; - } -} -` - }; - - const fileContentWithFS = ` -declare module "fs" { - export interface Stats { - isFile(): boolean; - } -} -`; - - const files = [root, file, libFile]; - const host = createWatchedSystem(files, { currentDirectory: "/a/b" }); - - const watch = createWatchOfFilesAndCompilerOptions([root.path, file.path], host, {}); - - checkOutputErrorsInitial(host, [ - getDiagnosticModuleNotFoundOfFile(watch(), root, "fs") - ]); - - file.content += fileContentWithFS; - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - checkOutputErrorsIncremental(host, emptyArray); - }); - - it("works when reusing program with files from external library", () => { - interface ExpectedFile { path: string; isExpectedToEmit?: boolean; content?: string; } - const configDir = "/a/b/projects/myProject/src/"; - const file1: File = { - path: configDir + "file1.ts", - content: 'import module1 = require("module1");\nmodule1("hello");' - }; - const file2: File = { - path: configDir + "file2.ts", - content: 'import module11 = require("module1");\nmodule11("hello");' - }; - const module1: File = { - path: "/a/b/projects/myProject/node_modules/module1/index.js", - content: "module.exports = options => { return options.toString(); }" - }; - const configFile: File = { - path: configDir + "tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - allowJs: true, - rootDir: ".", - outDir: "../dist", - moduleResolution: "node", - maxNodeModuleJsDepth: 1 - } - }) - }; - const outDirFolder = "/a/b/projects/myProject/dist/"; - const programFiles = [file1, file2, module1, libFile]; - const host = createWatchedSystem(programFiles.concat(configFile), { currentDirectory: "/a/b/projects/myProject/" }); - const watch = createWatchOfConfigFile(configFile.path, host); - checkProgramActualFiles(watch(), programFiles.map(f => f.path)); - checkOutputErrorsInitial(host, emptyArray); - const expectedFiles: ExpectedFile[] = [ - createExpectedEmittedFile(file1), - createExpectedEmittedFile(file2), - createExpectedToNotEmitFile("index.js"), - createExpectedToNotEmitFile("src/index.js"), - createExpectedToNotEmitFile("src/file1.js"), - createExpectedToNotEmitFile("src/file2.js"), - createExpectedToNotEmitFile("lib.js"), - createExpectedToNotEmitFile("lib.d.ts") - ]; - verifyExpectedFiles(expectedFiles); - - file1.content += "\n;"; - expectedFiles[0].content += ";\n"; // Only emit file1 with this change - expectedFiles[1].isExpectedToEmit = false; - host.reloadFS(programFiles.concat(configFile)); - host.runQueuedTimeoutCallbacks(); - checkProgramActualFiles(watch(), programFiles.map(f => f.path)); - checkOutputErrorsIncremental(host, emptyArray); - verifyExpectedFiles(expectedFiles); - - - function verifyExpectedFiles(expectedFiles: ExpectedFile[]) { - forEach(expectedFiles, f => { - assert.equal(!!host.fileExists(f.path), f.isExpectedToEmit, "File " + f.path + " is expected to " + (f.isExpectedToEmit ? "emit" : "not emit")); - if (f.isExpectedToEmit) { - assert.equal(host.readFile(f.path), f.content, "Expected contents of " + f.path); - } - }); - } - - function createExpectedToNotEmitFile(fileName: string): ExpectedFile { - return { - path: outDirFolder + fileName, - isExpectedToEmit: false - }; - } - - function createExpectedEmittedFile(file: File): ExpectedFile { - return { - path: removeFileExtension(file.path.replace(configDir, outDirFolder)) + Extension.Js, - isExpectedToEmit: true, - content: '"use strict";\nexports.__esModule = true;\n' + file.content.replace("import", "var") + "\n" - }; - } - }); - - it("works when renaming node_modules folder that already contains @types folder", () => { - const currentDirectory = "/user/username/projects/myproject"; - const file: File = { - path: `${currentDirectory}/a.ts`, - content: `import * as q from "qqq";` - }; - const module: File = { - path: `${currentDirectory}/node_modules2/@types/qqq/index.d.ts`, - content: "export {}" - }; - const files = [file, module, libFile]; - const host = createWatchedSystem(files, { currentDirectory }); - const watch = createWatchOfFilesAndCompilerOptions([file.path], host); - - checkProgramActualFiles(watch(), [file.path, libFile.path]); - checkOutputErrorsInitial(host, [getDiagnosticModuleNotFoundOfFile(watch(), file, "qqq")]); - checkWatchedDirectories(host, emptyArray, /*recursive*/ false); - checkWatchedDirectories(host, [`${currentDirectory}/node_modules`, `${currentDirectory}/node_modules/@types`], /*recursive*/ true); - - host.renameFolder(`${currentDirectory}/node_modules2`, `${currentDirectory}/node_modules`); - host.runQueuedTimeoutCallbacks(); - checkProgramActualFiles(watch(), [file.path, libFile.path, `${currentDirectory}/node_modules/@types/qqq/index.d.ts`]); - checkOutputErrorsIncremental(host, emptyArray); - }); - - describe("ignores files/folder changes in node_modules that start with '.'", () => { - const projectPath = "/user/username/projects/project"; - const npmCacheFile: File = { - path: `${projectPath}/node_modules/.cache/babel-loader/89c02171edab901b9926470ba6d5677e.ts`, - content: JSON.stringify({ something: 10 }) - }; - const file1: File = { - path: `${projectPath}/test.ts`, - content: `import { x } from "somemodule";` - }; - const file2: File = { - path: `${projectPath}/node_modules/somemodule/index.d.ts`, - content: `export const x = 10;` - }; - const files = [libFile, file1, file2]; - const expectedFiles = files.map(f => f.path); - it("when watching node_modules in inferred project for failed lookup", () => { - const host = createWatchedSystem(files); - const watch = createWatchOfFilesAndCompilerOptions([file1.path], host, {}, /*maxNumberOfFilesToIterateForInvalidation*/ 1); - checkProgramActualFiles(watch(), expectedFiles); - host.checkTimeoutQueueLength(0); - - host.ensureFileOrFolder(npmCacheFile); - host.checkTimeoutQueueLength(0); - }); - it("when watching node_modules as part of wild card directories in config project", () => { - const config: File = { - path: `${projectPath}/tsconfig.json`, - content: "{}" - }; - const host = createWatchedSystem(files.concat(config)); - const watch = createWatchOfConfigFile(config.path, host); - checkProgramActualFiles(watch(), expectedFiles); - host.checkTimeoutQueueLength(0); - - host.ensureFileOrFolder(npmCacheFile); - host.checkTimeoutQueueLength(0); - }); - }); - }); - - describe("resolutionCache:: tsc-watch with modules linked to sibling folder", () => { - const projectRoot = "/user/username/projects/project"; - const mainPackageRoot = `${projectRoot}/main`; - const linkedPackageRoot = `${projectRoot}/linked-package`; - const mainFile: File = { - path: `${mainPackageRoot}/index.ts`, - content: "import { Foo } from '@scoped/linked-package'" - }; - const config: File = { - path: `${mainPackageRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { module: "commonjs", moduleResolution: "node", baseUrl: ".", rootDir: "." }, - files: ["index.ts"] - }) - }; - const linkedPackageInMain: SymLink = { - path: `${mainPackageRoot}/node_modules/@scoped/linked-package`, - symLink: `${linkedPackageRoot}` - }; - const linkedPackageJson: File = { - path: `${linkedPackageRoot}/package.json`, - content: JSON.stringify({ name: "@scoped/linked-package", version: "0.0.1", types: "dist/index.d.ts", main: "dist/index.js" }) - }; - const linkedPackageIndex: File = { - path: `${linkedPackageRoot}/dist/index.d.ts`, - content: "export * from './other';" - }; - const linkedPackageOther: File = { - path: `${linkedPackageRoot}/dist/other.d.ts`, - content: 'export declare const Foo = "BAR";' - }; - - it("verify watched directories", () => { - const files = [libFile, mainFile, config, linkedPackageInMain, linkedPackageJson, linkedPackageIndex, linkedPackageOther]; - const host = createWatchedSystem(files, { currentDirectory: mainPackageRoot }); - createWatchOfConfigFile("tsconfig.json", host); - checkWatchedFilesDetailed(host, [libFile.path, mainFile.path, config.path, linkedPackageIndex.path, linkedPackageOther.path], 1); - checkWatchedDirectories(host, emptyArray, /*recursive*/ false); - checkWatchedDirectoriesDetailed(host, [`${mainPackageRoot}/@scoped`, `${mainPackageRoot}/node_modules`, linkedPackageRoot, `${mainPackageRoot}/node_modules/@types`, `${projectRoot}/node_modules/@types`], 1, /*recursive*/ true); - }); - }); -} - namespace ts.projectSystem { function createHostModuleResolutionTrace(host: TestServerHost & ModuleResolutionHost) { const resolutionTrace: string[] = []; diff --git a/src/testRunner/unittests/session.ts b/src/testRunner/unittests/tsserver/session.ts similarity index 100% rename from src/testRunner/unittests/session.ts rename to src/testRunner/unittests/tsserver/session.ts diff --git a/src/testRunner/unittests/tsserver/skipLibCheck.ts b/src/testRunner/unittests/tsserver/skipLibCheck.ts new file mode 100644 index 00000000000..463ea439396 --- /dev/null +++ b/src/testRunner/unittests/tsserver/skipLibCheck.ts @@ -0,0 +1,229 @@ +namespace ts.projectSystem { + describe("tsserver:: with skipLibCheck", () => { + it("should be turned on for js-only inferred projects", () => { + const file1 = { + path: "/a/b/file1.js", + content: ` + /// + var x = 1;` + }; + const file2 = { + path: "/a/b/file2.d.ts", + content: ` + interface T { + name: string; + }; + interface T { + name: number; + };` + }; + const host = createServerHost([file1, file2]); + const session = createSession(host); + openFilesForSession([file1, file2], session); + + const file2GetErrRequest = makeSessionRequest( + CommandNames.SemanticDiagnosticsSync, + { file: file2.path } + ); + let errorResult = session.executeCommand(file2GetErrRequest).response; + assert.isTrue(errorResult.length === 0); + + const closeFileRequest = makeSessionRequest(CommandNames.Close, { file: file1.path }); + session.executeCommand(closeFileRequest); + errorResult = session.executeCommand(file2GetErrRequest).response; + assert.isTrue(errorResult.length !== 0); + + openFilesForSession([file1], session); + errorResult = session.executeCommand(file2GetErrRequest).response; + assert.isTrue(errorResult.length === 0); + }); + + it("should be turned on for js-only external projects", () => { + const jsFile = { + path: "/a/b/file1.js", + content: "let x =1;" + }; + const dTsFile = { + path: "/a/b/file2.d.ts", + content: ` + interface T { + name: string; + }; + interface T { + name: number; + };` + }; + const host = createServerHost([jsFile, dTsFile]); + const session = createSession(host); + + const openExternalProjectRequest = makeSessionRequest( + CommandNames.OpenExternalProject, + { + projectFileName: "project1", + rootFiles: toExternalFiles([jsFile.path, dTsFile.path]), + options: {} + } + ); + session.executeCommand(openExternalProjectRequest); + + const dTsFileGetErrRequest = makeSessionRequest( + CommandNames.SemanticDiagnosticsSync, + { file: dTsFile.path } + ); + const errorResult = session.executeCommand(dTsFileGetErrRequest).response; + assert.isTrue(errorResult.length === 0); + }); + + it("should be turned on for js-only external projects with skipLibCheck=false", () => { + const jsFile = { + path: "/a/b/file1.js", + content: "let x =1;" + }; + const dTsFile = { + path: "/a/b/file2.d.ts", + content: ` + interface T { + name: string; + }; + interface T { + name: number; + };` + }; + const host = createServerHost([jsFile, dTsFile]); + const session = createSession(host); + + const openExternalProjectRequest = makeSessionRequest( + CommandNames.OpenExternalProject, + { + projectFileName: "project1", + rootFiles: toExternalFiles([jsFile.path, dTsFile.path]), + options: { skipLibCheck: false } + } + ); + session.executeCommand(openExternalProjectRequest); + + const dTsFileGetErrRequest = makeSessionRequest( + CommandNames.SemanticDiagnosticsSync, + { file: dTsFile.path } + ); + const errorResult = session.executeCommand(dTsFileGetErrRequest).response; + assert.isTrue(errorResult.length === 0); + }); + + it("should not report bind errors for declaration files with skipLibCheck=true", () => { + const jsconfigFile = { + path: "/a/jsconfig.json", + content: "{}" + }; + const jsFile = { + path: "/a/jsFile.js", + content: "let x = 1;" + }; + const dTsFile1 = { + path: "/a/dTsFile1.d.ts", + content: ` + declare var x: number;` + }; + const dTsFile2 = { + path: "/a/dTsFile2.d.ts", + content: ` + declare var x: string;` + }; + const host = createServerHost([jsconfigFile, jsFile, dTsFile1, dTsFile2]); + const session = createSession(host); + openFilesForSession([jsFile], session); + + const dTsFile1GetErrRequest = makeSessionRequest( + CommandNames.SemanticDiagnosticsSync, + { file: dTsFile1.path } + ); + const error1Result = session.executeCommand(dTsFile1GetErrRequest).response; + assert.isTrue(error1Result.length === 0); + + const dTsFile2GetErrRequest = makeSessionRequest( + CommandNames.SemanticDiagnosticsSync, + { file: dTsFile2.path } + ); + const error2Result = session.executeCommand(dTsFile2GetErrRequest).response; + assert.isTrue(error2Result.length === 0); + }); + + it("should report semantic errors for loose JS files with '// @ts-check' and skipLibCheck=true", () => { + const jsFile = { + path: "/a/jsFile.js", + content: ` + // @ts-check + let x = 1; + x === "string";` + }; + + const host = createServerHost([jsFile]); + const session = createSession(host); + openFilesForSession([jsFile], session); + + const getErrRequest = makeSessionRequest( + CommandNames.SemanticDiagnosticsSync, + { file: jsFile.path } + ); + const errorResult = session.executeCommand(getErrRequest).response; + assert.isTrue(errorResult.length === 1); + assert.equal(errorResult[0].code, Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap.code); + }); + + it("should report semantic errors for configured js project with '// @ts-check' and skipLibCheck=true", () => { + const jsconfigFile = { + path: "/a/jsconfig.json", + content: "{}" + }; + + const jsFile = { + path: "/a/jsFile.js", + content: ` + // @ts-check + let x = 1; + x === "string";` + }; + + const host = createServerHost([jsconfigFile, jsFile]); + const session = createSession(host); + openFilesForSession([jsFile], session); + + const getErrRequest = makeSessionRequest( + CommandNames.SemanticDiagnosticsSync, + { file: jsFile.path } + ); + const errorResult = session.executeCommand(getErrRequest).response; + assert.isTrue(errorResult.length === 1); + assert.equal(errorResult[0].code, Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap.code); + }); + + it("should report semantic errors for configured js project with checkJs=true and skipLibCheck=true", () => { + const jsconfigFile = { + path: "/a/jsconfig.json", + content: JSON.stringify({ + compilerOptions: { + checkJs: true, + skipLibCheck: true + }, + }) + }; + const jsFile = { + path: "/a/jsFile.js", + content: `let x = 1; + x === "string";` + }; + + const host = createServerHost([jsconfigFile, jsFile]); + const session = createSession(host); + openFilesForSession([jsFile], session); + + const getErrRequest = makeSessionRequest( + CommandNames.SemanticDiagnosticsSync, + { file: jsFile.path } + ); + const errorResult = session.executeCommand(getErrRequest).response; + assert.isTrue(errorResult.length === 1); + assert.equal(errorResult[0].code, Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap.code); + }); + }); +} diff --git a/src/testRunner/unittests/tsserverSymLinks.ts b/src/testRunner/unittests/tsserver/symLinks.ts similarity index 97% rename from src/testRunner/unittests/tsserverSymLinks.ts rename to src/testRunner/unittests/tsserver/symLinks.ts index c9bad7ea678..d6fa9dca894 100644 --- a/src/testRunner/unittests/tsserverSymLinks.ts +++ b/src/testRunner/unittests/tsserver/symLinks.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserverProjectSystem with symLinks", () => { + describe("tsserver:: symLinks", () => { it("rename in common file renames all project", () => { const projects = "/users/username/projects"; const folderA = `${projects}/a`; diff --git a/src/testRunner/unittests/telemetry.ts b/src/testRunner/unittests/tsserver/telemetry.ts similarity index 100% rename from src/testRunner/unittests/telemetry.ts rename to src/testRunner/unittests/tsserver/telemetry.ts diff --git a/src/testRunner/unittests/textStorage.ts b/src/testRunner/unittests/tsserver/textStorage.ts similarity index 100% rename from src/testRunner/unittests/textStorage.ts rename to src/testRunner/unittests/tsserver/textStorage.ts diff --git a/src/testRunner/unittests/typingsInstaller.ts b/src/testRunner/unittests/tsserver/typingsInstaller.ts similarity index 100% rename from src/testRunner/unittests/typingsInstaller.ts rename to src/testRunner/unittests/tsserver/typingsInstaller.ts diff --git a/src/testRunner/unittests/versionCache.ts b/src/testRunner/unittests/tsserver/versionCache.ts similarity index 100% rename from src/testRunner/unittests/versionCache.ts rename to src/testRunner/unittests/tsserver/versionCache.ts diff --git a/src/testRunner/unittests/tsserver/watchEnvironment.ts b/src/testRunner/unittests/tsserver/watchEnvironment.ts new file mode 100644 index 00000000000..8bce3505626 --- /dev/null +++ b/src/testRunner/unittests/tsserver/watchEnvironment.ts @@ -0,0 +1,135 @@ +namespace ts.projectSystem { + import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory; + describe("watchEnvironment:: tsserverProjectSystem watchDirectories implementation", () => { + function verifyCompletionListWithNewFileInSubFolder(tscWatchDirectory: Tsc_WatchDirectory) { + const projectFolder = "/a/username/project"; + const projectSrcFolder = `${projectFolder}/src`; + const configFile: File = { + path: `${projectFolder}/tsconfig.json`, + content: "{}" + }; + const index: File = { + path: `${projectSrcFolder}/index.ts`, + content: `import {} from "./"` + }; + const file1: File = { + path: `${projectSrcFolder}/file1.ts`, + content: "" + }; + + const files = [index, file1, configFile, libFile]; + const fileNames = files.map(file => file.path); + // All closed files(files other than index), project folder, project/src folder and project/node_modules/@types folder + const expectedWatchedFiles = arrayToMap(fileNames.slice(1), s => s, () => 1); + const expectedWatchedDirectories = createMap(); + const mapOfDirectories = tscWatchDirectory === Tsc_WatchDirectory.NonRecursiveWatchDirectory ? + expectedWatchedDirectories : + tscWatchDirectory === Tsc_WatchDirectory.WatchFile ? + expectedWatchedFiles : + createMap(); + // For failed resolution lookup and tsconfig files => cached so only watched only once + mapOfDirectories.set(projectFolder, 1); + // Through above recursive watches + mapOfDirectories.set(projectSrcFolder, 1); + // node_modules/@types folder + mapOfDirectories.set(`${projectFolder}/${nodeModulesAtTypes}`, 1); + const expectedCompletions = ["file1"]; + const completionPosition = index.content.lastIndexOf('"'); + const environmentVariables = createMap(); + environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory); + const host = createServerHost(files, { environmentVariables }); + const projectService = createProjectService(host); + projectService.openClientFile(index.path); + + const project = Debug.assertDefined(projectService.configuredProjects.get(configFile.path)); + verifyProjectAndCompletions(); + + // Add file2 + const file2: File = { + path: `${projectSrcFolder}/file2.ts`, + content: "" + }; + files.push(file2); + fileNames.push(file2.path); + expectedWatchedFiles.set(file2.path, 1); + expectedCompletions.push("file2"); + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + assert.equal(projectService.configuredProjects.get(configFile.path), project); + verifyProjectAndCompletions(); + + function verifyProjectAndCompletions() { + const completions = project.getLanguageService().getCompletionsAtPosition(index.path, completionPosition, { includeExternalModuleExports: false, includeInsertTextCompletions: false })!; + checkArray("Completion Entries", completions.entries.map(e => e.name), expectedCompletions); + + checkWatchedDirectories(host, emptyArray, /*recursive*/ true); + + checkWatchedFilesDetailed(host, expectedWatchedFiles); + checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ false); + checkProjectActualFiles(project, fileNames); + } + } + + it("uses watchFile when file is added to subfolder, completion list has new file", () => { + verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.WatchFile); + }); + + it("uses non recursive watchDirectory when file is added to subfolder, completion list has new file", () => { + verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.NonRecursiveWatchDirectory); + }); + + it("uses dynamic polling when file is added to subfolder, completion list has new file", () => { + verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.DynamicPolling); + }); + }); + + describe("watchEnvironment:: tsserverProjectSystem Watched recursive directories with windows style file system", () => { + function verifyWatchedDirectories(rootedPath: string, useProjectAtRoot: boolean) { + const root = useProjectAtRoot ? rootedPath : `${rootedPath}myfolder/allproject/`; + const configFile: File = { + path: root + "project/tsconfig.json", + content: "{}" + }; + const file1: File = { + path: root + "project/file1.ts", + content: "let x = 10;" + }; + const file2: File = { + path: root + "project/file2.ts", + content: "let y = 10;" + }; + const files = [configFile, file1, file2, libFile]; + const host = createServerHost(files, { useWindowsStylePaths: true }); + const projectService = createProjectService(host); + projectService.openClientFile(file1.path); + const project = projectService.configuredProjects.get(configFile.path)!; + assert.isDefined(project); + const winsowsStyleLibFilePath = "c:/" + libFile.path.substring(1); + checkProjectActualFiles(project, files.map(f => f === libFile ? winsowsStyleLibFilePath : f.path)); + checkWatchedFiles(host, mapDefined(files, f => f === libFile ? winsowsStyleLibFilePath : f === file1 ? undefined : f.path)); + checkWatchedDirectories(host, [], /*recursive*/ false); + checkWatchedDirectories(host, [ + root + "project", + root + "project/node_modules/@types" + ].concat(useProjectAtRoot ? [] : [root + nodeModulesAtTypes]), /*recursive*/ true); + } + + function verifyRootedDirectoryWatch(rootedPath: string) { + it("When project is in rootFolder of style c:/", () => { + verifyWatchedDirectories(rootedPath, /*useProjectAtRoot*/ true); + }); + + it("When files at some folder other than root", () => { + verifyWatchedDirectories(rootedPath, /*useProjectAtRoot*/ false); + }); + } + + describe("for rootFolder of style c:/", () => { + verifyRootedDirectoryWatch("c:/"); + }); + + describe("for rootFolder of style c:/users/username", () => { + verifyRootedDirectoryWatch("c:/users/username/"); + }); + }); +} diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 56493c01e35..85be445171a 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -3280,305 +3280,6 @@ var x = 10;` }); }); - describe("tsserverProjectSystem external projects", () => { - describe("correctly handling add/remove tsconfig - 1", () => { - function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1;" - }; - const f2 = { - path: "/a/b/lib.ts", - content: "" - }; - const tsconfig = { - path: "/a/b/tsconfig.json", - content: "" - }; - const host = createServerHost([f1, f2]); - const projectService = createProjectService(host); - projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); - - // open external project - const projectName = "/a/b/proj1"; - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, f2.path]), - options: {} - }); - projectService.openClientFile(f1.path); - projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]); - - // rename lib.ts to tsconfig.json - host.reloadFS([f1, tsconfig]); - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, tsconfig.path]), - options: {} - }); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - if (lazyConfiguredProjectsFromExternalProject) { - checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed - projectService.ensureInferredProjectsUpToDate_TestOnly(); - } - checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, tsconfig.path]); - - // rename tsconfig.json back to lib.ts - host.reloadFS([f1, f2]); - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, f2.path]), - options: {} - }); - - projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]); - } - it("when lazyConfiguredProjectsFromExternalProject not set", () => { - verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false); - }); - it("when lazyConfiguredProjectsFromExternalProject is set", () => { - verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ true); - }); - }); - - describe("correctly handling add/remove tsconfig - 2", () => { - function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1;" - }; - const cLib = { - path: "/a/b/c/lib.ts", - content: "" - }; - const cTsconfig = { - path: "/a/b/c/tsconfig.json", - content: "{}" - }; - const dLib = { - path: "/a/b/d/lib.ts", - content: "" - }; - const dTsconfig = { - path: "/a/b/d/tsconfig.json", - content: "{}" - }; - const host = createServerHost([f1, cLib, cTsconfig, dLib, dTsconfig]); - const projectService = createProjectService(host); - projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); - - // open external project - const projectName = "/a/b/proj1"; - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path]), - options: {} - }); - - projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], [f1.path]); - - // add two config file as root files - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]), - options: {} - }); - projectService.checkNumberOfProjects({ configuredProjects: 2 }); - if (lazyConfiguredProjectsFromExternalProject) { - checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed - checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed - projectService.ensureInferredProjectsUpToDate_TestOnly(); - } - checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]); - checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]); - - // remove one config file - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, dTsconfig.path]), - options: {} - }); - - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [dLib.path, dTsconfig.path]); - - // remove second config file - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path]), - options: {} - }); - - projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], [f1.path]); - - // open two config files - // add two config file as root files - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]), - options: {} - }); - projectService.checkNumberOfProjects({ configuredProjects: 2 }); - if (lazyConfiguredProjectsFromExternalProject) { - checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed - checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed - projectService.ensureInferredProjectsUpToDate_TestOnly(); - } - checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]); - checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]); - - // close all projects - no projects should be opened - projectService.closeExternalProject(projectName); - projectService.checkNumberOfProjects({}); - } - - it("when lazyConfiguredProjectsFromExternalProject not set", () => { - verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false); - }); - it("when lazyConfiguredProjectsFromExternalProject is set", () => { - verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ true); - }); - }); - - it("correctly handles changes in lib section of config file", () => { - const libES5 = { - path: "/compiler/lib.es5.d.ts", - content: "declare const eval: any" - }; - const libES2015Promise = { - path: "/compiler/lib.es2015.promise.d.ts", - content: "declare class Promise {}" - }; - const app = { - path: "/src/app.ts", - content: "var x: Promise;" - }; - const config1 = { - path: "/src/tsconfig.json", - content: JSON.stringify( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: true, - sourceMap: false, - lib: [ - "es5" - ] - } - }) - }; - const config2 = { - path: config1.path, - content: JSON.stringify( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: true, - sourceMap: false, - lib: [ - "es5", - "es2015.promise" - ] - } - }) - }; - const host = createServerHost([libES5, libES2015Promise, app, config1], { executingFilePath: "/compiler/tsc.js" }); - const projectService = createProjectService(host); - projectService.openClientFile(app.path); - - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [libES5.path, app.path, config1.path]); - - host.reloadFS([libES5, libES2015Promise, app, config2]); - host.checkTimeoutQueueLengthAndRun(2); - - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [libES5.path, libES2015Promise.path, app.path, config2.path]); - }); - - it("should handle non-existing directories in config file", () => { - const f = { - path: "/a/src/app.ts", - content: "let x = 1;" - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: {}, - include: [ - "src/**/*", - "notexistingfolder/*" - ] - }) - }; - const host = createServerHost([f, config]); - const projectService = createProjectService(host); - projectService.openClientFile(f.path); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - const project = projectService.configuredProjects.get(config.path)!; - assert.isTrue(project.hasOpenRef()); // f - - projectService.closeClientFile(f.path); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(config.path), project); - assert.isFalse(project.hasOpenRef()); // No files - assert.isFalse(project.isClosed()); - - projectService.openClientFile(f.path); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(config.path), project); - assert.isTrue(project.hasOpenRef()); // f - assert.isFalse(project.isClosed()); - }); - - it("handles loads existing configured projects of external projects when lazyConfiguredProjectsFromExternalProject is disabled", () => { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1" - }; - const config = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({}) - }; - const projectFileName = "/a/b/project.csproj"; - const host = createServerHost([f1, config]); - const service = createProjectService(host); - service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: true } }); - service.openExternalProject({ - projectFileName, - rootFiles: toExternalFiles([f1.path, config.path]), - options: {} - }); - service.checkNumberOfProjects({ configuredProjects: 1 }); - const project = service.configuredProjects.get(config.path)!; - assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded - checkProjectActualFiles(project, emptyArray); - - service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: false } }); - assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded - checkProjectActualFiles(project, [config.path, f1.path]); - - service.closeExternalProject(projectFileName); - service.checkNumberOfProjects({}); - - service.openExternalProject({ - projectFileName, - rootFiles: toExternalFiles([f1.path, config.path]), - options: {} - }); - service.checkNumberOfProjects({ configuredProjects: 1 }); - const project2 = service.configuredProjects.get(config.path)!; - assert.equal(project2.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded - checkProjectActualFiles(project2, [config.path, f1.path]); - }); - }); - describe("tsserverProjectSystem prefer typings to js", () => { it("during second resolution pass", () => { const typingsCacheLocation = "/a/typings"; @@ -3770,234 +3471,6 @@ var x = 10;` }); }); - describe("tsserverProjectSystem skipLibCheck", () => { - it("should be turned on for js-only inferred projects", () => { - const file1 = { - path: "/a/b/file1.js", - content: ` - /// - var x = 1;` - }; - const file2 = { - path: "/a/b/file2.d.ts", - content: ` - interface T { - name: string; - }; - interface T { - name: number; - };` - }; - const host = createServerHost([file1, file2]); - const session = createSession(host); - openFilesForSession([file1, file2], session); - - const file2GetErrRequest = makeSessionRequest( - CommandNames.SemanticDiagnosticsSync, - { file: file2.path } - ); - let errorResult = session.executeCommand(file2GetErrRequest).response; - assert.isTrue(errorResult.length === 0); - - const closeFileRequest = makeSessionRequest(CommandNames.Close, { file: file1.path }); - session.executeCommand(closeFileRequest); - errorResult = session.executeCommand(file2GetErrRequest).response; - assert.isTrue(errorResult.length !== 0); - - openFilesForSession([file1], session); - errorResult = session.executeCommand(file2GetErrRequest).response; - assert.isTrue(errorResult.length === 0); - }); - - it("should be turned on for js-only external projects", () => { - const jsFile = { - path: "/a/b/file1.js", - content: "let x =1;" - }; - const dTsFile = { - path: "/a/b/file2.d.ts", - content: ` - interface T { - name: string; - }; - interface T { - name: number; - };` - }; - const host = createServerHost([jsFile, dTsFile]); - const session = createSession(host); - - const openExternalProjectRequest = makeSessionRequest( - CommandNames.OpenExternalProject, - { - projectFileName: "project1", - rootFiles: toExternalFiles([jsFile.path, dTsFile.path]), - options: {} - } - ); - session.executeCommand(openExternalProjectRequest); - - const dTsFileGetErrRequest = makeSessionRequest( - CommandNames.SemanticDiagnosticsSync, - { file: dTsFile.path } - ); - const errorResult = session.executeCommand(dTsFileGetErrRequest).response; - assert.isTrue(errorResult.length === 0); - }); - - it("should be turned on for js-only external projects with skipLibCheck=false", () => { - const jsFile = { - path: "/a/b/file1.js", - content: "let x =1;" - }; - const dTsFile = { - path: "/a/b/file2.d.ts", - content: ` - interface T { - name: string; - }; - interface T { - name: number; - };` - }; - const host = createServerHost([jsFile, dTsFile]); - const session = createSession(host); - - const openExternalProjectRequest = makeSessionRequest( - CommandNames.OpenExternalProject, - { - projectFileName: "project1", - rootFiles: toExternalFiles([jsFile.path, dTsFile.path]), - options: { skipLibCheck: false } - } - ); - session.executeCommand(openExternalProjectRequest); - - const dTsFileGetErrRequest = makeSessionRequest( - CommandNames.SemanticDiagnosticsSync, - { file: dTsFile.path } - ); - const errorResult = session.executeCommand(dTsFileGetErrRequest).response; - assert.isTrue(errorResult.length === 0); - }); - - it("should not report bind errors for declaration files with skipLibCheck=true", () => { - const jsconfigFile = { - path: "/a/jsconfig.json", - content: "{}" - }; - const jsFile = { - path: "/a/jsFile.js", - content: "let x = 1;" - }; - const dTsFile1 = { - path: "/a/dTsFile1.d.ts", - content: ` - declare var x: number;` - }; - const dTsFile2 = { - path: "/a/dTsFile2.d.ts", - content: ` - declare var x: string;` - }; - const host = createServerHost([jsconfigFile, jsFile, dTsFile1, dTsFile2]); - const session = createSession(host); - openFilesForSession([jsFile], session); - - const dTsFile1GetErrRequest = makeSessionRequest( - CommandNames.SemanticDiagnosticsSync, - { file: dTsFile1.path } - ); - const error1Result = session.executeCommand(dTsFile1GetErrRequest).response; - assert.isTrue(error1Result.length === 0); - - const dTsFile2GetErrRequest = makeSessionRequest( - CommandNames.SemanticDiagnosticsSync, - { file: dTsFile2.path } - ); - const error2Result = session.executeCommand(dTsFile2GetErrRequest).response; - assert.isTrue(error2Result.length === 0); - }); - - it("should report semantic errors for loose JS files with '// @ts-check' and skipLibCheck=true", () => { - const jsFile = { - path: "/a/jsFile.js", - content: ` - // @ts-check - let x = 1; - x === "string";` - }; - - const host = createServerHost([jsFile]); - const session = createSession(host); - openFilesForSession([jsFile], session); - - const getErrRequest = makeSessionRequest( - CommandNames.SemanticDiagnosticsSync, - { file: jsFile.path } - ); - const errorResult = session.executeCommand(getErrRequest).response; - assert.isTrue(errorResult.length === 1); - assert.equal(errorResult[0].code, Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap.code); - }); - - it("should report semantic errors for configured js project with '// @ts-check' and skipLibCheck=true", () => { - const jsconfigFile = { - path: "/a/jsconfig.json", - content: "{}" - }; - - const jsFile = { - path: "/a/jsFile.js", - content: ` - // @ts-check - let x = 1; - x === "string";` - }; - - const host = createServerHost([jsconfigFile, jsFile]); - const session = createSession(host); - openFilesForSession([jsFile], session); - - const getErrRequest = makeSessionRequest( - CommandNames.SemanticDiagnosticsSync, - { file: jsFile.path } - ); - const errorResult = session.executeCommand(getErrRequest).response; - assert.isTrue(errorResult.length === 1); - assert.equal(errorResult[0].code, Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap.code); - }); - - it("should report semantic errors for configured js project with checkJs=true and skipLibCheck=true", () => { - const jsconfigFile = { - path: "/a/jsconfig.json", - content: JSON.stringify({ - compilerOptions: { - checkJs: true, - skipLibCheck: true - }, - }) - }; - const jsFile = { - path: "/a/jsFile.js", - content: `let x = 1; - x === "string";` - }; - - const host = createServerHost([jsconfigFile, jsFile]); - const session = createSession(host); - openFilesForSession([jsFile], session); - - const getErrRequest = makeSessionRequest( - CommandNames.SemanticDiagnosticsSync, - { file: jsFile.path } - ); - const errorResult = session.executeCommand(getErrRequest).response; - assert.isTrue(errorResult.length === 1); - assert.equal(errorResult[0].code, Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap.code); - }); - }); - describe("tsserverProjectSystem non-existing directories listed in config file input array", () => { it("should be tolerated without crashing the server", () => { const configFile = { @@ -4079,156 +3552,6 @@ var x = 10;` }); }); - describe("tsserverProjectSystem reload", () => { - it("should work with temp file", () => { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1" - }; - const tmp = { - path: "/a/b/app.tmp", - content: "const y = 42" - }; - const host = createServerHost([f1, tmp]); - const session = createSession(host); - - // send open request - session.executeCommand({ - type: "request", - command: "open", - seq: 1, - arguments: { file: f1.path } - }); - - // reload from tmp file - session.executeCommand({ - type: "request", - command: "reload", - seq: 2, - arguments: { file: f1.path, tmpfile: tmp.path } - }); - - // verify content - const projectServiice = session.getProjectService(); - const snap1 = projectServiice.getScriptInfo(f1.path)!.getSnapshot(); - assert.equal(getSnapshotText(snap1), tmp.content, "content should be equal to the content of temp file"); - - // reload from original file file - session.executeCommand({ - type: "request", - command: "reload", - seq: 2, - arguments: { file: f1.path } - }); - - // verify content - const snap2 = projectServiice.getScriptInfo(f1.path)!.getSnapshot(); - assert.equal(getSnapshotText(snap2), f1.content, "content should be equal to the content of original file"); - - }); - - it("should work when script info doesnt have any project open", () => { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1" - }; - const tmp = { - path: "/a/b/app.tmp", - content: "const y = 42" - }; - const host = createServerHost([f1, tmp, libFile]); - const session = createSession(host); - const openContent = "let z = 1"; - // send open request - session.executeCommandSeq({ - command: server.protocol.CommandTypes.Open, - arguments: { file: f1.path, fileContent: openContent } - }); - - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - const info = projectService.getScriptInfo(f1.path)!; - assert.isDefined(info); - checkScriptInfoContents(openContent, "contents set during open request"); - - // send close request - session.executeCommandSeq({ - command: server.protocol.CommandTypes.Close, - arguments: { file: f1.path } - }); - checkScriptInfoAndProjects(f1.content, "contents of closed file"); - checkInferredProjectIsOrphan(); - - // Can reload contents of the file when its not open and has no project - // reload from temp file - session.executeCommandSeq({ - command: server.protocol.CommandTypes.Reload, - arguments: { file: f1.path, tmpfile: tmp.path } - }); - checkScriptInfoAndProjects(tmp.content, "contents of temp file"); - checkInferredProjectIsOrphan(); - - // reload from own file - session.executeCommandSeq({ - command: server.protocol.CommandTypes.Reload, - arguments: { file: f1.path } - }); - checkScriptInfoAndProjects(f1.content, "contents of closed file"); - checkInferredProjectIsOrphan(); - - // Open file again without setting its content - session.executeCommandSeq({ - command: server.protocol.CommandTypes.Open, - arguments: { file: f1.path } - }); - checkScriptInfoAndProjects(f1.content, "contents of file when opened without specifying contents"); - const snap = info.getSnapshot(); - - // send close request - session.executeCommandSeq({ - command: server.protocol.CommandTypes.Close, - arguments: { file: f1.path } - }); - checkScriptInfoAndProjects(f1.content, "contents of closed file"); - assert.strictEqual(info.getSnapshot(), snap); - checkInferredProjectIsOrphan(); - - // reload from temp file - session.executeCommandSeq({ - command: server.protocol.CommandTypes.Reload, - arguments: { file: f1.path, tmpfile: tmp.path } - }); - checkScriptInfoAndProjects(tmp.content, "contents of temp file"); - assert.notStrictEqual(info.getSnapshot(), snap); - checkInferredProjectIsOrphan(); - - // reload from own file - session.executeCommandSeq({ - command: server.protocol.CommandTypes.Reload, - arguments: { file: f1.path } - }); - checkScriptInfoAndProjects(f1.content, "contents of closed file"); - assert.notStrictEqual(info.getSnapshot(), snap); - checkInferredProjectIsOrphan(); - - function checkInferredProjectIsOrphan() { - assert.isTrue(projectService.inferredProjects[0].isOrphan()); - assert.equal(info.containingProjects.length, 0); - } - - function checkScriptInfoAndProjects(contentsOfInfo: string, captionForContents: string) { - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - assert.strictEqual(projectService.getScriptInfo(f1.path), info); - checkScriptInfoContents(contentsOfInfo, captionForContents); - } - - function checkScriptInfoContents(contentsOfInfo: string, captionForContents: string) { - const snap = info.getSnapshot(); - assert.equal(getSnapshotText(snap), contentsOfInfo, "content should be equal to " + captionForContents); - } - }); - }); - describe("tsserverProjectSystem Inferred projects", () => { it("should support files without extensions", () => { const f = { diff --git a/src/testRunner/unittests/watchEnvironment.ts b/src/testRunner/unittests/watchEnvironment.ts deleted file mode 100644 index 74a6637d6fc..00000000000 --- a/src/testRunner/unittests/watchEnvironment.ts +++ /dev/null @@ -1,316 +0,0 @@ -namespace ts { - import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory; - - export namespace tscWatch { - describe("watchEnvironment:: tsc-watch with different polling/non polling options", () => { - it("watchFile using dynamic priority polling", () => { - const projectFolder = "/a/username/project"; - const file1: File = { - path: `${projectFolder}/typescript.ts`, - content: "var z = 10;" - }; - const files = [file1, libFile]; - const environmentVariables = createMap(); - environmentVariables.set("TSC_WATCHFILE", "DynamicPriorityPolling"); - const host = createWatchedSystem(files, { environmentVariables }); - const watch = createWatchOfFilesAndCompilerOptions([file1.path], host); - - const initialProgram = watch(); - verifyProgram(); - - const mediumPollingIntervalThreshold = unchangedPollThresholds[PollingInterval.Medium]; - for (let index = 0; index < mediumPollingIntervalThreshold; index++) { - // Transition libFile and file1 to low priority queue - host.checkTimeoutQueueLengthAndRun(1); - assert.deepEqual(watch(), initialProgram); - } - - // Make a change to file - file1.content = "var zz30 = 100;"; - host.reloadFS(files); - - // This should detect change in the file - host.checkTimeoutQueueLengthAndRun(1); - assert.deepEqual(watch(), initialProgram); - - // Callbacks: medium priority + high priority queue and scheduled program update - host.checkTimeoutQueueLengthAndRun(3); - // During this timeout the file would be detected as unchanged - let fileUnchangeDetected = 1; - const newProgram = watch(); - assert.notStrictEqual(newProgram, initialProgram); - - verifyProgram(); - const outputFile1 = changeExtension(file1.path, ".js"); - assert.isTrue(host.fileExists(outputFile1)); - assert.equal(host.readFile(outputFile1), file1.content + host.newLine); - - const newThreshold = unchangedPollThresholds[PollingInterval.Low] + mediumPollingIntervalThreshold; - for (; fileUnchangeDetected < newThreshold; fileUnchangeDetected++) { - // For high + Medium/low polling interval - host.checkTimeoutQueueLengthAndRun(2); - assert.deepEqual(watch(), newProgram); - } - - // Everything goes in high polling interval queue - host.checkTimeoutQueueLengthAndRun(1); - assert.deepEqual(watch(), newProgram); - - function verifyProgram() { - checkProgramActualFiles(watch(), files.map(f => f.path)); - checkWatchedFiles(host, []); - checkWatchedDirectories(host, [], /*recursive*/ false); - checkWatchedDirectories(host, [], /*recursive*/ true); - } - }); - - describe("tsc-watch when watchDirectories implementation", () => { - function verifyRenamingFileInSubFolder(tscWatchDirectory: Tsc_WatchDirectory) { - const projectFolder = "/a/username/project"; - const projectSrcFolder = `${projectFolder}/src`; - const configFile: File = { - path: `${projectFolder}/tsconfig.json`, - content: "{}" - }; - const file: File = { - path: `${projectSrcFolder}/file1.ts`, - content: "" - }; - const programFiles = [file, libFile]; - const files = [file, configFile, libFile]; - const environmentVariables = createMap(); - environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory); - const host = createWatchedSystem(files, { environmentVariables }); - const watch = createWatchOfConfigFile(configFile.path, host); - const projectFolders = [projectFolder, projectSrcFolder, `${projectFolder}/node_modules/@types`]; - // Watching files config file, file, lib file - const expectedWatchedFiles = files.map(f => f.path); - const expectedWatchedDirectories = tscWatchDirectory === Tsc_WatchDirectory.NonRecursiveWatchDirectory ? projectFolders : emptyArray; - if (tscWatchDirectory === Tsc_WatchDirectory.WatchFile) { - expectedWatchedFiles.push(...projectFolders); - } - - verifyProgram(checkOutputErrorsInitial); - - // Rename the file: - file.path = file.path.replace("file1.ts", "file2.ts"); - expectedWatchedFiles[0] = file.path; - host.reloadFS(files); - if (tscWatchDirectory === Tsc_WatchDirectory.DynamicPolling) { - // With dynamic polling the fs change would be detected only by running timeouts - host.runQueuedTimeoutCallbacks(); - } - // Delayed update program - host.runQueuedTimeoutCallbacks(); - verifyProgram(checkOutputErrorsIncremental); - - function verifyProgram(checkOutputErrors: (host: WatchedSystem, errors: ReadonlyArray) => void) { - checkProgramActualFiles(watch(), programFiles.map(f => f.path)); - checkOutputErrors(host, emptyArray); - - const outputFile = changeExtension(file.path, ".js"); - assert(host.fileExists(outputFile)); - assert.equal(host.readFile(outputFile), file.content); - - checkWatchedDirectories(host, emptyArray, /*recursive*/ true); - - // Watching config file, file, lib file and directories - checkWatchedFilesDetailed(host, expectedWatchedFiles, 1); - checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, 1, /*recursive*/ false); - } - } - - it("uses watchFile when renaming file in subfolder", () => { - verifyRenamingFileInSubFolder(Tsc_WatchDirectory.WatchFile); - }); - - it("uses non recursive watchDirectory when renaming file in subfolder", () => { - verifyRenamingFileInSubFolder(Tsc_WatchDirectory.NonRecursiveWatchDirectory); - }); - - it("uses non recursive dynamic polling when renaming file in subfolder", () => { - verifyRenamingFileInSubFolder(Tsc_WatchDirectory.DynamicPolling); - }); - - it("when there are symlinks to folders in recursive folders", () => { - const cwd = "/home/user/projects/myproject"; - const file1: File = { - path: `${cwd}/src/file.ts`, - content: `import * as a from "a"` - }; - const tsconfig: File = { - path: `${cwd}/tsconfig.json`, - content: `{ "compilerOptions": { "extendedDiagnostics": true, "traceResolution": true }}` - }; - const realA: File = { - path: `${cwd}/node_modules/reala/index.d.ts`, - content: `export {}` - }; - const realB: File = { - path: `${cwd}/node_modules/realb/index.d.ts`, - content: `export {}` - }; - const symLinkA: SymLink = { - path: `${cwd}/node_modules/a`, - symLink: `${cwd}/node_modules/reala` - }; - const symLinkB: SymLink = { - path: `${cwd}/node_modules/b`, - symLink: `${cwd}/node_modules/realb` - }; - const symLinkBInA: SymLink = { - path: `${cwd}/node_modules/reala/node_modules/b`, - symLink: `${cwd}/node_modules/b` - }; - const symLinkAInB: SymLink = { - path: `${cwd}/node_modules/realb/node_modules/a`, - symLink: `${cwd}/node_modules/a` - }; - const files = [file1, tsconfig, realA, realB, symLinkA, symLinkB, symLinkBInA, symLinkAInB]; - const environmentVariables = createMap(); - environmentVariables.set("TSC_WATCHDIRECTORY", Tsc_WatchDirectory.NonRecursiveWatchDirectory); - const host = createWatchedSystem(files, { environmentVariables, currentDirectory: cwd }); - createWatchOfConfigFile("tsconfig.json", host); - checkWatchedDirectories(host, emptyArray, /*recursive*/ true); - checkWatchedDirectories(host, [cwd, `${cwd}/node_modules`, `${cwd}/node_modules/@types`, `${cwd}/node_modules/reala`, `${cwd}/node_modules/realb`, - `${cwd}/node_modules/reala/node_modules`, `${cwd}/node_modules/realb/node_modules`, `${cwd}/src`], /*recursive*/ false); - }); - }); - }); - } - - export namespace projectSystem { - describe("watchEnvironment:: tsserverProjectSystem watchDirectories implementation", () => { - function verifyCompletionListWithNewFileInSubFolder(tscWatchDirectory: Tsc_WatchDirectory) { - const projectFolder = "/a/username/project"; - const projectSrcFolder = `${projectFolder}/src`; - const configFile: File = { - path: `${projectFolder}/tsconfig.json`, - content: "{}" - }; - const index: File = { - path: `${projectSrcFolder}/index.ts`, - content: `import {} from "./"` - }; - const file1: File = { - path: `${projectSrcFolder}/file1.ts`, - content: "" - }; - - const files = [index, file1, configFile, libFile]; - const fileNames = files.map(file => file.path); - // All closed files(files other than index), project folder, project/src folder and project/node_modules/@types folder - const expectedWatchedFiles = arrayToMap(fileNames.slice(1), s => s, () => 1); - const expectedWatchedDirectories = createMap(); - const mapOfDirectories = tscWatchDirectory === Tsc_WatchDirectory.NonRecursiveWatchDirectory ? - expectedWatchedDirectories : - tscWatchDirectory === Tsc_WatchDirectory.WatchFile ? - expectedWatchedFiles : - createMap(); - // For failed resolution lookup and tsconfig files => cached so only watched only once - mapOfDirectories.set(projectFolder, 1); - // Through above recursive watches - mapOfDirectories.set(projectSrcFolder, 1); - // node_modules/@types folder - mapOfDirectories.set(`${projectFolder}/${nodeModulesAtTypes}`, 1); - const expectedCompletions = ["file1"]; - const completionPosition = index.content.lastIndexOf('"'); - const environmentVariables = createMap(); - environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory); - const host = createServerHost(files, { environmentVariables }); - const projectService = createProjectService(host); - projectService.openClientFile(index.path); - - const project = Debug.assertDefined(projectService.configuredProjects.get(configFile.path)); - verifyProjectAndCompletions(); - - // Add file2 - const file2: File = { - path: `${projectSrcFolder}/file2.ts`, - content: "" - }; - files.push(file2); - fileNames.push(file2.path); - expectedWatchedFiles.set(file2.path, 1); - expectedCompletions.push("file2"); - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - assert.equal(projectService.configuredProjects.get(configFile.path), project); - verifyProjectAndCompletions(); - - function verifyProjectAndCompletions() { - const completions = project.getLanguageService().getCompletionsAtPosition(index.path, completionPosition, { includeExternalModuleExports: false, includeInsertTextCompletions: false })!; - checkArray("Completion Entries", completions.entries.map(e => e.name), expectedCompletions); - - checkWatchedDirectories(host, emptyArray, /*recursive*/ true); - - checkWatchedFilesDetailed(host, expectedWatchedFiles); - checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ false); - checkProjectActualFiles(project, fileNames); - } - } - - it("uses watchFile when file is added to subfolder, completion list has new file", () => { - verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.WatchFile); - }); - - it("uses non recursive watchDirectory when file is added to subfolder, completion list has new file", () => { - verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.NonRecursiveWatchDirectory); - }); - - it("uses dynamic polling when file is added to subfolder, completion list has new file", () => { - verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.DynamicPolling); - }); - }); - - describe("watchEnvironment:: tsserverProjectSystem Watched recursive directories with windows style file system", () => { - function verifyWatchedDirectories(rootedPath: string, useProjectAtRoot: boolean) { - const root = useProjectAtRoot ? rootedPath : `${rootedPath}myfolder/allproject/`; - const configFile: File = { - path: root + "project/tsconfig.json", - content: "{}" - }; - const file1: File = { - path: root + "project/file1.ts", - content: "let x = 10;" - }; - const file2: File = { - path: root + "project/file2.ts", - content: "let y = 10;" - }; - const files = [configFile, file1, file2, libFile]; - const host = createServerHost(files, { useWindowsStylePaths: true }); - const projectService = createProjectService(host); - projectService.openClientFile(file1.path); - const project = projectService.configuredProjects.get(configFile.path)!; - assert.isDefined(project); - const winsowsStyleLibFilePath = "c:/" + libFile.path.substring(1); - checkProjectActualFiles(project, files.map(f => f === libFile ? winsowsStyleLibFilePath : f.path)); - checkWatchedFiles(host, mapDefined(files, f => f === libFile ? winsowsStyleLibFilePath : f === file1 ? undefined : f.path)); - checkWatchedDirectories(host, [], /*recursive*/ false); - checkWatchedDirectories(host, [ - root + "project", - root + "project/node_modules/@types" - ].concat(useProjectAtRoot ? [] : [root + nodeModulesAtTypes]), /*recursive*/ true); - } - - function verifyRootedDirectoryWatch(rootedPath: string) { - it("When project is in rootFolder of style c:/", () => { - verifyWatchedDirectories(rootedPath, /*useProjectAtRoot*/ true); - }); - - it("When files at some folder other than root", () => { - verifyWatchedDirectories(rootedPath, /*useProjectAtRoot*/ false); - }); - } - - describe("for rootFolder of style c:/", () => { - verifyRootedDirectoryWatch("c:/"); - }); - - describe("for rootFolder of style c:/users/username", () => { - verifyRootedDirectoryWatch("c:/users/username/"); - }); - }); - } -}