diff --git a/src/compiler/program.ts b/src/compiler/program.ts index abbc935cf65..1c7430e3300 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -66,20 +66,20 @@ namespace ts { mtime: Date; } - export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost { + export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean, system = sys): CompilerHost { const existingDirectories = createMap(); function getCanonicalFileName(fileName: string): string { // if underlying system can distinguish between two files whose names differs only in cases then file name already in canonical form. // otherwise use toLowerCase as a canonical form. - return sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(); + return system.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(); } function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile | undefined { let text: string | undefined; try { performance.mark("beforeIORead"); - text = sys.readFile(fileName, options.charset); + text = system.readFile(fileName, options.charset); performance.mark("afterIORead"); performance.measure("I/O Read", "beforeIORead", "afterIORead"); } @@ -97,7 +97,7 @@ namespace ts { if (existingDirectories.has(directoryPath)) { return true; } - if (sys.directoryExists(directoryPath)) { + if (system.directoryExists(directoryPath)) { existingDirectories.set(directoryPath, true); return true; } @@ -108,7 +108,7 @@ namespace ts { if (directoryPath.length > getRootLength(directoryPath) && !directoryExists(directoryPath)) { const parentDirectory = getDirectoryPath(directoryPath); ensureDirectoriesExist(parentDirectory); - sys.createDirectory(directoryPath); + system.createDirectory(directoryPath); } } @@ -119,8 +119,8 @@ namespace ts { outputFingerprints = createMap(); } - const hash = sys.createHash!(data); // TODO: GH#18217 - const mtimeBefore = sys.getModifiedTime!(fileName); // TODO: GH#18217 + const hash = system.createHash!(data); // TODO: GH#18217 + const mtimeBefore = system.getModifiedTime!(fileName); // TODO: GH#18217 if (mtimeBefore) { const fingerprint = outputFingerprints.get(fileName); @@ -133,9 +133,9 @@ namespace ts { } } - sys.writeFile(fileName, data, writeByteOrderMark); + system.writeFile(fileName, data, writeByteOrderMark); - const mtimeAfter = sys.getModifiedTime!(fileName) || missingFileModifiedTime; // TODO: GH#18217 + const mtimeAfter = system.getModifiedTime!(fileName) || missingFileModifiedTime; // TODO: GH#18217 outputFingerprints.set(fileName, { hash, @@ -149,11 +149,11 @@ namespace ts { performance.mark("beforeIOWrite"); ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); - if (isWatchSet(options) && sys.createHash && sys.getModifiedTime) { + if (isWatchSet(options) && system.createHash && system.getModifiedTime) { writeFileIfUpdated(fileName, data, writeByteOrderMark); } else { - sys.writeFile(fileName, data, writeByteOrderMark); + system.writeFile(fileName, data, writeByteOrderMark); } performance.mark("afterIOWrite"); @@ -167,32 +167,32 @@ namespace ts { } function getDefaultLibLocation(): string { - return getDirectoryPath(normalizePath(sys.getExecutingFilePath())); + return getDirectoryPath(normalizePath(system.getExecutingFilePath())); } const newLine = getNewLineCharacter(options); - const realpath = sys.realpath && ((path: string) => sys.realpath!(path)); + const realpath = system.realpath && ((path: string) => system.realpath!(path)); return { getSourceFile, getDefaultLibLocation, getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), writeFile, - getCurrentDirectory: memoize(() => sys.getCurrentDirectory()), - useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames, + getCurrentDirectory: memoize(() => system.getCurrentDirectory()), + useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, getCanonicalFileName, getNewLine: () => newLine, - fileExists: fileName => sys.fileExists(fileName), - readFile: fileName => sys.readFile(fileName), - trace: (s: string) => sys.write(s + newLine), - directoryExists: directoryName => sys.directoryExists(directoryName), - getEnvironmentVariable: name => sys.getEnvironmentVariable ? sys.getEnvironmentVariable(name) : "", - getDirectories: (path: string) => sys.getDirectories(path), + fileExists: fileName => system.fileExists(fileName), + readFile: fileName => system.readFile(fileName), + trace: (s: string) => system.write(s + newLine), + directoryExists: directoryName => system.directoryExists(directoryName), + getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "", + getDirectories: (path: string) => system.getDirectories(path), realpath, - readDirectory: (path, extensions, include, exclude, depth) => sys.readDirectory(path, extensions, include, exclude, depth), - getModifiedTime: sys.getModifiedTime && (path => sys.getModifiedTime!(path)), - setModifiedTime: sys.setModifiedTime && ((path, date) => sys.setModifiedTime!(path, date)), - deleteFile: sys.deleteFile && (path => sys.deleteFile!(path)) + readDirectory: (path, extensions, include, exclude, depth) => system.readDirectory(path, extensions, include, exclude, depth), + getModifiedTime: system.getModifiedTime && (path => system.getModifiedTime!(path)), + setModifiedTime: system.setModifiedTime && ((path, date) => system.setModifiedTime!(path, date)), + deleteFile: system.deleteFile && (path => system.deleteFile!(path)) }; } diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index 2a2ed4ef7dc..57fa33a84c2 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -620,14 +620,14 @@ interface Array {}` } } - removeFile(filePath: string) { + deleteFile(filePath: string) { const path = this.toFullPath(filePath); const currentEntry = this.fs.get(path) as FsFile; Debug.assert(isFsFile(currentEntry)); this.removeFileOrFolder(currentEntry, returnFalse); } - removeFolder(folderPath: string, recursive?: boolean) { + deleteFolder(folderPath: string, recursive?: boolean) { const path = this.toFullPath(folderPath); const currentEntry = this.fs.get(path) as FsFolder; Debug.assert(isFsFolder(currentEntry)); @@ -635,7 +635,7 @@ interface Array {}` const subEntries = currentEntry.entries.slice(); subEntries.forEach(fsEntry => { if (isFsFolder(fsEntry)) { - this.removeFolder(fsEntry.fullPath, recursive); + this.deleteFolder(fsEntry.fullPath, recursive); } else { this.removeFileOrFolder(fsEntry, returnFalse); @@ -766,6 +766,14 @@ interface Array {}` return (fsEntry && fsEntry.modifiedTime)!; // TODO: GH#18217 } + setModifiedTime(s: string, date: Date) { + const path = this.toFullPath(s); + const fsEntry = this.fs.get(path); + if (fsEntry) { + fsEntry.modifiedTime = date; + } + } + readFile(s: string): string | undefined { const fsEntry = this.getRealFile(this.toFullPath(s)); return fsEntry ? fsEntry.content : undefined; diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 12c671f9cf0..fa7900f1ca3 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -80,6 +80,7 @@ "unittests/transform.ts", "unittests/transpile.ts", "unittests/tsbuild.ts", + "unittests/tsbuildWatchMode.ts", "unittests/tsconfigParsing.ts", "unittests/tscWatchMode.ts", "unittests/versionCache.ts", diff --git a/src/testRunner/unittests/tsbuildWatchMode.ts b/src/testRunner/unittests/tsbuildWatchMode.ts new file mode 100644 index 00000000000..9f608180d03 --- /dev/null +++ b/src/testRunner/unittests/tsbuildWatchMode.ts @@ -0,0 +1,69 @@ +namespace ts.tscWatch { + export import libFile = TestFSWithWatch.libFile; + function createSolutionBuilder(host: WatchedSystem, rootNames: ReadonlyArray, defaultOptions?: BuildOptions) { + const compilerHost = createCompilerHost({}, /*setParentNodes*/ undefined, host); + const reportDiag = createDiagnosticReporter(host); + const report = (message: DiagnosticMessage, ...args: string[]) => reportDiag(createCompilerDiagnostic(message, ...args)); + const buildHost: BuildHost = { + error: report, + verbose: report, + message: report, + errorDiagnostic: d => reportDiag(d) + }; + return ts.createSolutionBuilder(compilerHost, buildHost, rootNames, defaultOptions || { dry: false, force: false, verbose: false }, host); + } + + function createSolutionBuilderWithWatch(host: WatchedSystem, rootNames: ReadonlyArray, defaultOptions?: BuildOptions) { + const solutionBuilder = createSolutionBuilder(host, rootNames, defaultOptions); + solutionBuilder.buildAllProjects(); + solutionBuilder.startWatching(); + return solutionBuilder; + } + + describe("tsbuild-watch program updates", () => { + const projectsLocation = "/user/username/projects"; + const project = "sample1"; + const enum SubProject { + core = "core", + logic = "logic", + tests = "tests", + ui = "ui" + } + type ReadonlyFile = Readonly; + /** [tsconfig, index] | [tsconfig, index, anotherModule, someDecl] */ + type SubProjectFiles = [ReadonlyFile, ReadonlyFile] | [ReadonlyFile, ReadonlyFile, ReadonlyFile, ReadonlyFile]; + const root = Harness.IO.getWorkspaceRoot(); + function projectFile(subProject: SubProject, baseFileName: string): File { + return { + path: `${projectsLocation}/${project}/${subProject}/${baseFileName.toLowerCase()}`, + content: Harness.IO.readFile(`${root}/tests/projects/${project}/${subProject}/${baseFileName}`)! + }; + } + function subProjectFiles(subProject: SubProject, anotherModuleAndSomeDecl?: true): SubProjectFiles { + const tsconfig = projectFile(subProject, "tsconfig.json"); + const index = projectFile(subProject, "index.ts"); + if (!anotherModuleAndSomeDecl) { + return [tsconfig, index]; + } + const anotherModule = projectFile(SubProject.core, "anotherModule.ts"); + const someDecl = projectFile(SubProject.core, "some_decl.ts"); + return [tsconfig, index, anotherModule, someDecl]; + } + + const core = subProjectFiles(SubProject.core, /*anotherModuleAndSomeDecl*/ true); + const logic = subProjectFiles(SubProject.logic); + const tests = subProjectFiles(SubProject.tests); + const ui = subProjectFiles(SubProject.ui); + const allFiles: ReadonlyArray = [libFile, ...core, ...logic, ...tests, ...ui]; + const testProjectExpectedWatchedFiles = [core[0], core[1], core[2], ...logic, ...tests].map(f => f.path); + it("creates solution in watch mode", () => { + const host = createWatchedSystem(allFiles, { currentDirectory: projectsLocation }); + const originalWrite = host.write; + host.write = s => { console.log(s); originalWrite.call(host, s); }; + createSolutionBuilderWithWatch(host, [`${project}/${SubProject.tests}`]); + checkWatchedFiles(host, testProjectExpectedWatchedFiles); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectories(host, emptyArray, /*recursive*/ true); // TODO: #26524 + }); + }); +} diff --git a/src/testRunner/unittests/tscWatchMode.ts b/src/testRunner/unittests/tscWatchMode.ts index d91559c72fc..51c0d888f0b 100644 --- a/src/testRunner/unittests/tscWatchMode.ts +++ b/src/testRunner/unittests/tscWatchMode.ts @@ -1,17 +1,16 @@ namespace ts.tscWatch { - import WatchedSystem = TestFSWithWatch.TestServerHost; - type File = TestFSWithWatch.File; - type SymLink = TestFSWithWatch.SymLink; - import createWatchedSystem = TestFSWithWatch.createWatchedSystem; - import checkArray = TestFSWithWatch.checkArray; - import libFile = TestFSWithWatch.libFile; - import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles; - import checkWatchedFilesDetailed = TestFSWithWatch.checkWatchedFilesDetailed; - import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories; - import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed; - import checkOutputContains = TestFSWithWatch.checkOutputContains; - import checkOutputDoesNotContain = TestFSWithWatch.checkOutputDoesNotContain; - import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory; + export import WatchedSystem = TestFSWithWatch.TestServerHost; + export type File = TestFSWithWatch.File; + export type SymLink = TestFSWithWatch.SymLink; + export import createWatchedSystem = TestFSWithWatch.createWatchedSystem; + export import checkArray = TestFSWithWatch.checkArray; + export import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles; + export import checkWatchedFilesDetailed = TestFSWithWatch.checkWatchedFilesDetailed; + export import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories; + export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed; + export import checkOutputContains = TestFSWithWatch.checkOutputContains; + export import checkOutputDoesNotContain = TestFSWithWatch.checkOutputDoesNotContain; + export import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory; export function checkProgramActualFiles(program: Program, expectedFiles: string[]) { checkArray(`Program actual files`, program.getSourceFiles().map(file => file.fileName), expectedFiles); diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index b99a2d9fe99..5491d83965d 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -8257,7 +8257,7 @@ new C();` verifyProjectWithResolvedModule(session); - host.removeFolder(recognizersTextDist, /*recursive*/ true); + host.deleteFolder(recognizersTextDist, /*recursive*/ true); host.runQueuedTimeoutCallbacks(); verifyProjectWithUnresolvedModule(session); @@ -9173,7 +9173,7 @@ describe("Test Suite 1", () => { checkProjectActualFiles(project, expectedFilesWithUnitTest1); const navBarResultUnitTest1 = navBarFull(session, unitTest1); - host.removeFile(unitTest1.path); + host.deleteFile(unitTest1.path); host.checkTimeoutQueueLengthAndRun(2); checkProjectActualFiles(project, expectedFilesWithoutUnitTest1); @@ -9297,7 +9297,7 @@ export function Test2() { checkDeclarationFiles(bTs, session, [bDtsMap, bDts]); // Testing what happens if we delete the original sources. - host.removeFile(bTs.path); + host.deleteFile(bTs.path); openFilesForSession([userTs], session); const service = session.getProjectService(); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 5e992f3847a..e41db162ae2 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4125,7 +4125,7 @@ declare namespace ts { declare namespace ts { function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string | undefined; function resolveTripleslashReference(moduleName: string, containingFile: string): string; - function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost; + function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean, system?: System): CompilerHost; function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; interface FormatDiagnosticsHost { getCurrentDirectory(): string; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 32761156558..fca9d26c306 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4125,7 +4125,7 @@ declare namespace ts { declare namespace ts { function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string | undefined; function resolveTripleslashReference(moduleName: string, containingFile: string): string; - function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost; + function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean, system?: System): CompilerHost; function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; interface FormatDiagnosticsHost { getCurrentDirectory(): string;