diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 0c8444daa2fc9..59b57bfa99a41 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -96,35 +96,51 @@ namespace ts { if (existingDirectories.has(directoryPath)) { return true; } - if (system.directoryExists(directoryPath)) { + if ((compilerHost.directoryExists || system.directoryExists)(directoryPath)) { existingDirectories.set(directoryPath, true); return true; } return false; } - function ensureDirectoriesExist(directoryPath: string) { - if (directoryPath.length > getRootLength(directoryPath) && !directoryExists(directoryPath)) { - const parentDirectory = getDirectoryPath(directoryPath); - ensureDirectoriesExist(parentDirectory); - if (compilerHost.createDirectory) { - compilerHost.createDirectory(directoryPath); - } - else { - system.createDirectory(directoryPath); + function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { + try { + performance.mark("beforeIOWrite"); + + // NOTE: If patchWriteFileEnsuringDirectory has been called, + // the system.writeFile will do its own directory creation and + // the ensureDirectoriesExist call will always be redundant. + writeFileEnsuringDirectories( + fileName, + data, + writeByteOrderMark, + (path, data, writeByteOrderMark) => writeFileWorker(path, data, writeByteOrderMark), + path => (compilerHost.createDirectory || system.createDirectory)(path), + path => directoryExists(path)); + + performance.mark("afterIOWrite"); + performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); + } + catch (e) { + if (onError) { + onError(e.message); } } } let outputFingerprints: Map; + function writeFileWorker(fileName: string, data: string, writeByteOrderMark: boolean) { + if (!isWatchSet(options) || !system.createHash || !system.getModifiedTime) { + system.writeFile(fileName, data, writeByteOrderMark); + return; + } - function writeFileIfUpdated(fileName: string, data: string, writeByteOrderMark: boolean): void { if (!outputFingerprints) { outputFingerprints = createMap(); } - const hash = system.createHash!(data); // TODO: GH#18217 - const mtimeBefore = system.getModifiedTime!(fileName); // TODO: GH#18217 + const hash = system.createHash(data); + const mtimeBefore = system.getModifiedTime(fileName); if (mtimeBefore) { const fingerprint = outputFingerprints.get(fileName); @@ -139,7 +155,7 @@ namespace ts { system.writeFile(fileName, data, writeByteOrderMark); - const mtimeAfter = system.getModifiedTime!(fileName) || missingFileModifiedTime; // TODO: GH#18217 + const mtimeAfter = system.getModifiedTime(fileName) || missingFileModifiedTime; outputFingerprints.set(fileName, { hash, @@ -148,28 +164,6 @@ namespace ts { }); } - function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { - try { - performance.mark("beforeIOWrite"); - ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); - - if (isWatchSet(options) && system.createHash && system.getModifiedTime) { - writeFileIfUpdated(fileName, data, writeByteOrderMark); - } - else { - system.writeFile(fileName, data, writeByteOrderMark); - } - - performance.mark("afterIOWrite"); - performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); - } - catch (e) { - if (onError) { - onError(e.message); - } - } - } - function getDefaultLibLocation(): string { return getDirectoryPath(normalizePath(system.getExecutingFilePath())); } diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index ad866cba13065..31b422a12b9ef 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -522,17 +522,6 @@ namespace ts { } } - function recursiveCreateDirectory(directoryPath: string, sys: System) { - const basePath = getDirectoryPath(directoryPath); - const shouldCreateParent = basePath !== "" && directoryPath !== basePath && !sys.directoryExists(basePath); - if (shouldCreateParent) { - recursiveCreateDirectory(basePath, sys); - } - if (shouldCreateParent || !sys.directoryExists(directoryPath)) { - sys.createDirectory(directoryPath); - } - } - /** * patch writefile to create folder before writing the file */ @@ -540,13 +529,14 @@ namespace ts { export function patchWriteFileEnsuringDirectory(sys: System) { // patch writefile to create folder before writing the file const originalWriteFile = sys.writeFile; - sys.writeFile = (path, data, writeBom) => { - const directoryPath = getDirectoryPath(normalizeSlashes(path)); - if (directoryPath && !sys.directoryExists(directoryPath)) { - recursiveCreateDirectory(directoryPath, sys); - } - originalWriteFile.call(sys, path, data, writeBom); - }; + sys.writeFile = (path, data, writeBom) => + writeFileEnsuringDirectories( + path, + data, + !!writeBom, + (path, data, writeByteOrderMark) => originalWriteFile.call(sys, path, data, writeByteOrderMark), + path => sys.createDirectory(path), + path => sys.directoryExists(path)); } /*@internal*/ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 2fb1173deef3a..ac29da9389989 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3700,6 +3700,36 @@ namespace ts { }, sourceFiles); } + function ensureDirectoriesExist( + directoryPath: string, + createDirectory: (path: string) => void, + directoryExists: (path: string) => boolean): void { + if (directoryPath.length > getRootLength(directoryPath) && !directoryExists(directoryPath)) { + const parentDirectory = getDirectoryPath(directoryPath); + ensureDirectoriesExist(parentDirectory, createDirectory, directoryExists); + createDirectory(directoryPath); + } + } + + export function writeFileEnsuringDirectories( + path: string, + data: string, + writeByteOrderMark: boolean, + writeFile: (path: string, data: string, writeByteOrderMark: boolean) => void, + createDirectory: (path: string) => void, + directoryExists: (path: string) => boolean): void { + + // PERF: Checking for directory existence is expensive. Instead, assume the directory exists + // and fall back to creating it if the file write fails. + try { + writeFile(path, data, writeByteOrderMark); + } + catch { + ensureDirectoriesExist(getDirectoryPath(normalizePath(path)), createDirectory, directoryExists); + writeFile(path, data, writeByteOrderMark); + } + } + export function getLineOfLocalPosition(currentSourceFile: SourceFile, pos: number) { return getLineAndCharacterOfPosition(currentSourceFile, pos).line; } diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 06871fc9cacdb..5e8991dbb61e0 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -296,20 +296,20 @@ namespace ts { readDirectory: maybeBind(host, host.readDirectory), }; - function ensureDirectoriesExist(directoryPath: string) { - if (directoryPath.length > getRootLength(directoryPath) && !host.directoryExists!(directoryPath)) { - const parentDirectory = getDirectoryPath(directoryPath); - ensureDirectoriesExist(parentDirectory); - if (host.createDirectory) host.createDirectory(directoryPath); - } - } - function writeFile(fileName: string, text: string, writeByteOrderMark: boolean, onError: (message: string) => void) { try { performance.mark("beforeIOWrite"); - ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); - host.writeFile!(fileName, text, writeByteOrderMark); + // NOTE: If patchWriteFileEnsuringDirectory has been called, + // the host.writeFile will do its own directory creation and + // the ensureDirectoriesExist call will always be redundant. + writeFileEnsuringDirectories( + fileName, + text, + writeByteOrderMark, + (path, data, writeByteOrderMark) => host.writeFile!(path, data, writeByteOrderMark), + path => host.createDirectory!(path), + path => host.directoryExists!(path)); performance.mark("afterIOWrite"); performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite");