diff --git a/src/compiler/program.ts b/src/compiler/program.ts index b4b31226837..5b39c79aeac 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -190,9 +190,9 @@ namespace ts { getDirectories: (path: string) => sys.getDirectories(path), realpath, readDirectory: (path, extensions, include, exclude, depth) => sys.readDirectory(path, extensions, include, exclude, depth), - getModifiedTime: path => sys.getModifiedTime(path), - setModifiedTime: (path, date) => sys.setModifiedTime(path, date), - deleteFile: path => sys.deleteFile(path) + getModifiedTime: sys.getModifiedTime && (path => sys.getModifiedTime!(path)), + setModifiedTime: sys.setModifiedTime && ((path, date) => sys.setModifiedTime!(path, date)), + deleteFile: sys.deleteFile && (path => sys.deleteFile!(path)) }; } diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index b2ccdbd5f6c..bbd8e68301f 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -248,14 +248,14 @@ namespace ts { } function getOutputDeclarationFileName(inputFileName: string, configFile: ParsedCommandLine) { - const relativePath = getRelativePathFromDirectory(rootDirOfOptions(configFile.options, configFile.options.configFilePath), inputFileName, /*ignoreCase*/ true); - const outputPath = resolvePath(configFile.options.declarationDir || configFile.options.outDir || getDirectoryPath(configFile.options.configFilePath), relativePath); + const relativePath = getRelativePathFromDirectory(rootDirOfOptions(configFile.options, configFile.options.configFilePath!), inputFileName, /*ignoreCase*/ true); + const outputPath = resolvePath(configFile.options.declarationDir || configFile.options.outDir || getDirectoryPath(configFile.options.configFilePath!), relativePath); return changeExtension(outputPath, ".d.ts"); } function getOutputJavaScriptFileName(inputFileName: string, configFile: ParsedCommandLine) { - const relativePath = getRelativePathFromDirectory(rootDirOfOptions(configFile.options, configFile.options.configFilePath), inputFileName, /*ignoreCase*/ true); - const outputPath = resolvePath(configFile.options.outDir || getDirectoryPath(configFile.options.configFilePath), relativePath); + const relativePath = getRelativePathFromDirectory(rootDirOfOptions(configFile.options, configFile.options.configFilePath!), inputFileName, /*ignoreCase*/ true); + const outputPath = resolvePath(configFile.options.outDir || getDirectoryPath(configFile.options.configFilePath!), relativePath); return changeExtension(outputPath, (fileExtensionIs(inputFileName, ".tsx") && configFile.options.jsx === JsxEmit.Preserve) ? ".jsx" : ".js"); } @@ -276,7 +276,9 @@ namespace ts { } function getOutFileOutputs(project: ParsedCommandLine): ReadonlyArray { - Debug.assert(!!project.options.outFile, "outFile must be set"); + if (!project.options.outFile) { + throw new Error("Assert - outFile must be set"); + } const outputs: string[] = []; outputs.push(project.options.outFile); if (project.options.declaration) { @@ -328,9 +330,7 @@ namespace ts { options, projectStatus: createFileMap(), unchangedOutputs: createFileMap(), - verbose: options.verbose ? (diag, ...args) => { - verboseDiag(createCompilerDiagnostic(diag, ...args)); - } : () => undefined + verbose: verboseDiag ? (diag, ...args) => verboseDiag(createCompilerDiagnostic(diag, ...args)) : () => undefined }; } @@ -379,6 +379,11 @@ namespace ts { function addProject(projectSpecification: string) { const fileName = resolvePath(host.getCurrentDirectory(), projectSpecification); const refPath = resolveProjectReferencePath(host, { path: fileName }); + if (!refPath) { + reportDiagnostic(createCompilerDiagnostic(Diagnostics.File_0_does_not_exist, projectSpecification)); + return; + } + if (!host.fileExists(refPath)) { reportDiagnostic(createCompilerDiagnostic(Diagnostics.File_0_does_not_exist, fileName)); } @@ -388,6 +393,10 @@ namespace ts { } export function createSolutionBuilder(host: CompilerHost, reportDiagnostic: DiagnosticReporter, options: BuildOptions) { + if (!host.getModifiedTime || !host.setModifiedTime) { + throw new Error("Host must support timestamp APIs"); + } + const configFileCache = createConfigFileCache(host); let context = createBuildContext(options, reportDiagnostic); @@ -407,13 +416,17 @@ namespace ts { return getUpToDateStatus(configFileCache.parseConfigFile(configFileName)); } - function getUpToDateStatus(project: ParsedCommandLine): UpToDateStatus { - const prior = context.projectStatus.getValueOrUndefined(project.options.configFilePath); + function getUpToDateStatus(project: ParsedCommandLine | undefined): UpToDateStatus { + if (project === undefined) { + return { type: UpToDateStatusType.Unbuildable, reason: "File deleted mid-build" }; + } + + const prior = context.projectStatus.getValueOrUndefined(project.options.configFilePath!); if (prior !== undefined) { return prior; } const actual = getUpToDateStatusWorker(project); - context.projectStatus.setValue(project.options.configFilePath, actual); + context.projectStatus.setValue(project.options.configFilePath!, actual); return actual; } @@ -442,7 +455,7 @@ namespace ts { }; } - const inputTime = host.getModifiedTime(inputFile); + const inputTime = host.getModifiedTime!(inputFile); if (inputTime > newestInputFileTime) { newestInputFileName = inputFile; newestInputFileTime = inputTime; @@ -466,7 +479,7 @@ namespace ts { }; } - const outputTime = host.getModifiedTime(output); + const outputTime = host.getModifiedTime!(output); // If an output is older than the newest input, we can stop checking if (outputTime < newestInputFileTime) { return { @@ -492,7 +505,7 @@ namespace ts { newestDeclarationFileContentChangedTime = newer(unchangedTime, newestDeclarationFileContentChangedTime); } else { - newestDeclarationFileContentChangedTime = newer(newestDeclarationFileContentChangedTime, host.getModifiedTime(output)); + newestDeclarationFileContentChangedTime = newer(newestDeclarationFileContentChangedTime, host.getModifiedTime!(output)); } } } @@ -609,10 +622,10 @@ namespace ts { } // TODO Accept parsedCommandLine instead? - function buildSingleProject(proj: ResolvedConfigFileName) { + function buildSingleProject(proj: ResolvedConfigFileName): BuildResultFlags { if (context.options.dry) { reportDiagnostic(createCompilerDiagnostic(Diagnostics.Would_build_project_0, proj)); - return; + return BuildResultFlags.Success; } context.verbose(Diagnostics.Building_project_0, proj); @@ -707,17 +720,17 @@ namespace ts { let priorNewestUpdateTime = minimumDate; for (const file of outputs) { if (isDeclarationFile(file)) { - priorNewestUpdateTime = newer(priorNewestUpdateTime, host.getModifiedTime(file)); + priorNewestUpdateTime = newer(priorNewestUpdateTime, host.getModifiedTime!(file)); } - host.setModifiedTime(file, now); + host.setModifiedTime!(file, now); } - context.projectStatus.setValue(proj.options.configFilePath, { type: UpToDateStatusType.UpToDate, newestDeclarationFileContentChangedTime: priorNewestUpdateTime } as UpToDateStatus); + context.projectStatus.setValue(proj.options.configFilePath!, { type: UpToDateStatusType.UpToDate, newestDeclarationFileContentChangedTime: priorNewestUpdateTime } as UpToDateStatus); } function getFilesToClean(configFileNames: ResolvedConfigFileName[]): string[] | undefined { const resolvedNames: ResolvedConfigFileName[] | undefined = resolveProjectNames(configFileNames); - if (resolvedNames === undefined) return; + if (resolvedNames === undefined) return undefined; // Get the same graph for cleaning we'd use for building const graph = createDependencyGraph(resolvedNames); @@ -726,6 +739,10 @@ namespace ts { for (const level of graph.buildQueue) { for (const proj of level) { const parsed = configFileCache.parseConfigFile(proj); + if (parsed === undefined) { + // File has gone missing; fine to ignore here + continue; + } const outputs = getAllProjectOutputs(parsed); for (const output of outputs) { if (host.fileExists(output)) { @@ -742,6 +759,9 @@ namespace ts { if (resolvedNames === undefined) return; const filesToDelete = getFilesToClean(resolvedNames); + if (filesToDelete === undefined) { + return; + } if (context.options.dry) { reportDiagnostic(createCompilerDiagnostic(Diagnostics.Would_delete_the_following_files_Colon_0, filesToDelete.map(f => `\r\n * ${f}`).join(""))); @@ -786,9 +806,13 @@ namespace ts { const queue = graph.buildQueue; reportBuildQueue(graph); - let next: ResolvedConfigFileName; + let next: ResolvedConfigFileName | undefined; while (next = getNext()) { const proj = configFileCache.parseConfigFile(next); + if (proj === undefined) { + break; + } + const status = getUpToDateStatus(proj); reportProjectStatus(next, status); diff --git a/src/harness/unittests/tsbuild.ts b/src/harness/unittests/tsbuild.ts index 1596cc4ec19..4570c7598e7 100644 --- a/src/harness/unittests/tsbuild.ts +++ b/src/harness/unittests/tsbuild.ts @@ -7,7 +7,7 @@ namespace ts { const sampleRoot = resolvePath(__dirname, "../../tests/projects/sample1"); loadFsMirror(bfs, sampleRoot, "/src"); bfs.mkdirpSync("/lib"); - bfs.writeFileSync("/lib/lib.d.ts", Harness.IO.readFile(combinePaths(Harness.libFolder, "lib.d.ts"))); + bfs.writeFileSync("/lib/lib.d.ts", Harness.IO.readFile(combinePaths(Harness.libFolder, "lib.d.ts"))!); bfs.meta.set("defaultLibLocation", "/lib"); bfs.makeReadonly(); tick(); @@ -196,7 +196,7 @@ namespace ts { function assertDiagnosticMessages(...expected: DiagnosticMessage[]) { const actual = lastDiagnostics.slice(); if (actual.length !== expected.length) { - assert.fail(actual, expected, `Diagnostic arrays did not match - expected\r\n${actual.map(a => " " + a.messageText).join("\r\n")}\r\ngot\r\n${expected.map(e => " " + e.message).join("\r\n")}`); + assert.fail(actual, expected, `Diagnostic arrays did not match - got\r\n${actual.map(a => " " + a.messageText).join("\r\n")}\r\nexpected\r\n${expected.map(e => " " + e.message).join("\r\n")}`); } for (let i = 0; i < actual.length; i++) { if (actual[i].code !== expected[i].code) { @@ -229,7 +229,7 @@ namespace ts { vfs.mkdirpSync(virtualRoot); for (const path of Harness.IO.readDirectory(localRoot)) { const file = getBaseFileName(path); - vfs.writeFileSync(virtualRoot + "/" + file, Harness.IO.readFile(localRoot + "/" + file)); + vfs.writeFileSync(virtualRoot + "/" + file, Harness.IO.readFile(localRoot + "/" + file)!); } for (const dir of Harness.IO.getDirectories(localRoot)) { loadFsMirror(vfs, localRoot + "/" + dir, virtualRoot + "/" + dir); diff --git a/src/harness/vfs.ts b/src/harness/vfs.ts index 09b2e39cd88..be8b6c3fed7 100644 --- a/src/harness/vfs.ts +++ b/src/harness/vfs.ts @@ -415,6 +415,9 @@ namespace vfs { */ public utimesSync(path: string, atime: Date, mtime: Date) { const entry = this._walk(this._resolve(path)); + if (!entry || !entry.node) { + throw createIOError("ENOENT"); + } entry.node.atimeMs = +atime; entry.node.mtimeMs = +mtime; }