From 6385f7e3bb2c97118211754b2a11ff2356fe11ab Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 7 Aug 2017 14:47:32 -0700 Subject: [PATCH] Get semantic diagnostics for the program from builder so that it caches the errors of unchanged files --- src/compiler/builder.ts | 57 ++++++++++- src/compiler/resolutionCache.ts | 2 +- src/compiler/tsc.ts | 25 ++++- src/compiler/watchedProgram.ts | 174 +++++++++++++++----------------- 4 files changed, 160 insertions(+), 98 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index c0af4f5b13d..7d4b1ce37a2 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -26,6 +26,7 @@ namespace ts { getFilesAffectedBy(program: Program, path: Path): string[]; emitFile(program: Program, path: Path): EmitOutput; emitChangedFiles(program: Program): EmitOutputDetailed[]; + getSemanticDiagnostics(program: Program, cancellationToken?: CancellationToken): Diagnostic[]; clear(): void; } @@ -74,6 +75,7 @@ namespace ts { // Last checked shape signature for the file info type FileInfo = { version: string; signature: string; }; let fileInfos: Map; + const semanticDiagnosticsPerFile = createMap(); let changedFilesSinceLastEmit: Map; let emitHandler: EmitHandler; return { @@ -81,6 +83,7 @@ namespace ts { getFilesAffectedBy, emitFile, emitChangedFiles, + getSemanticDiagnostics, clear }; @@ -90,6 +93,7 @@ namespace ts { isModuleEmit = currentIsModuleEmit; emitHandler = isModuleEmit ? getModuleEmitHandler() : getNonModuleEmitHandler(); fileInfos = undefined; + semanticDiagnosticsPerFile.clear(); } changedFilesSinceLastEmit = changedFilesSinceLastEmit || createMap(); @@ -104,20 +108,27 @@ namespace ts { ); } + function registerChangedFile(path: Path) { + changedFilesSinceLastEmit.set(path, true); + // All changed files need to re-evaluate its semantic diagnostics + semanticDiagnosticsPerFile.delete(path); + } + function addNewFileInfo(program: Program, sourceFile: SourceFile): FileInfo { - changedFilesSinceLastEmit.set(sourceFile.path, true); + registerChangedFile(sourceFile.path); emitHandler.addScriptInfo(program, sourceFile); return { version: sourceFile.version, signature: undefined }; } function removeExistingFileInfo(path: Path, _existingFileInfo: FileInfo) { - changedFilesSinceLastEmit.set(path, true); + registerChangedFile(path); emitHandler.removeScriptInfo(path); } function updateExistingFileInfo(program: Program, existingInfo: FileInfo, sourceFile: SourceFile, hasInvalidatedResolution: HasInvalidatedResolution) { if (existingInfo.version !== sourceFile.version || hasInvalidatedResolution(sourceFile.path)) { - changedFilesSinceLastEmit.set(sourceFile.path, true); + registerChangedFile(sourceFile.path); + semanticDiagnosticsPerFile.delete(sourceFile.path); existingInfo.version = sourceFile.version; emitHandler.updateScriptInfo(program, sourceFile); } @@ -170,6 +181,9 @@ namespace ts { const sourceFile = program.getSourceFile(file); seenFiles.set(file, sourceFile); if (sourceFile) { + // Any affected file shouldnt have the cached diagnostics + semanticDiagnosticsPerFile.delete(sourceFile.path); + const emitOutput = getEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ false, /*isDetailed*/ true) as EmitOutputDetailed; result.push(emitOutput); @@ -189,10 +203,45 @@ namespace ts { return result; } + function getSemanticDiagnostics(program: Program, cancellationToken?: CancellationToken): Diagnostic[] { + ensureProgramGraph(program); + + // Ensure that changed files have cleared their respective + if (changedFilesSinceLastEmit) { + changedFilesSinceLastEmit.forEach((__value, path: Path) => { + const affectedFiles = getFilesAffectedBy(program, path); + for (const file of affectedFiles) { + const sourceFile = program.getSourceFile(file); + if (sourceFile) { + semanticDiagnosticsPerFile.delete(sourceFile.path); + } + } + }); + } + + let diagnostics: Diagnostic[]; + for (const sourceFile of program.getSourceFiles()) { + const path = sourceFile.path; + const cachedDiagnostics = semanticDiagnosticsPerFile.get(path); + // Report the semantic diagnostics from the cache if we already have those diagnostics present + if (cachedDiagnostics) { + diagnostics = concatenate(diagnostics, cachedDiagnostics); + } + else { + // Diagnostics werent cached, get them from program, and cache the result + const cachedDiagnostics = program.getSemanticDiagnostics(sourceFile, cancellationToken); + semanticDiagnosticsPerFile.set(path, cachedDiagnostics); + diagnostics = concatenate(diagnostics, cachedDiagnostics); + } + } + return diagnostics || emptyArray; + } + function clear() { isModuleEmit = undefined; emitHandler = undefined; fileInfos = undefined; + semanticDiagnosticsPerFile.clear(); } /** @@ -356,7 +405,7 @@ namespace ts { const referencedByPaths = referencedBy.get(path); if (referencedByPaths) { for (const path of referencedByPaths) { - changedFilesSinceLastEmit.set(path, true); + registerChangedFile(path); } referencedBy.delete(path); } diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index b6f3e7547f1..615340e4872 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -276,7 +276,7 @@ namespace ts { if (resolution && !resolution.isInvalidated) { const result = getResult(resolution); if (result) { - if (getResultFileName(result) === deletedFilePath) { + if (toPath(getResultFileName(result)) === deletedFilePath) { resolution.isInvalidated = true; (filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap())).set(path, true); } diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index b89a41a2d23..78586e5479a 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -157,7 +157,7 @@ namespace ts { enableStatistics(compilerOptions); const program = createProgram(rootFileNames, compilerOptions, compilerHost); - const exitStatus = compileProgram(sys, program, () => program.emit(), reportDiagnostic); + const exitStatus = compileProgram(program); reportStatistics(program); return sys.exit(exitStatus); @@ -174,6 +174,29 @@ namespace ts { return watchingHost; } + function compileProgram(program: Program): ExitStatus { + let diagnostics: Diagnostic[]; + + // First get and report any syntactic errors. + diagnostics = program.getSyntacticDiagnostics(); + + // If we didn't have any syntactic errors, then also try getting the global and + // semantic errors. + if (diagnostics.length === 0) { + diagnostics = program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics()); + + if (diagnostics.length === 0) { + diagnostics = program.getSemanticDiagnostics(); + } + } + + // Emit and report any errors we ran into. + const { emittedFiles, emitSkipped, diagnostics: emitDiagnostics } = program.emit(); + diagnostics = diagnostics.concat(emitDiagnostics); + + return handleEmitOutputAndReportErrors(sys, program, emittedFiles, emitSkipped, diagnostics, reportDiagnostic); + } + function enableStatistics(compilerOptions: CompilerOptions) { if (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics) { performance.enable(); diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts index 8628b6d049b..d4301ea839d 100644 --- a/src/compiler/watchedProgram.ts +++ b/src/compiler/watchedProgram.ts @@ -101,29 +101,12 @@ namespace ts { } } - export function compileProgram(system: System, program: Program, emitProgram: () => EmitResult, - reportDiagnostic: DiagnosticReporter): ExitStatus { - let diagnostics: Diagnostic[]; - - // First get and report any syntactic errors. - diagnostics = program.getSyntacticDiagnostics(); - - // If we didn't have any syntactic errors, then also try getting the global and - // semantic errors. - if (diagnostics.length === 0) { - diagnostics = program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics()); - - if (diagnostics.length === 0) { - diagnostics = program.getSemanticDiagnostics(); - } - } - - // Emit and report any errors we ran into. - const emitOutput = emitProgram(); - diagnostics = diagnostics.concat(emitOutput.diagnostics); - + export function handleEmitOutputAndReportErrors(system: System, program: Program, + emittedFiles: string[], emitSkipped: boolean, + diagnostics: Diagnostic[], reportDiagnostic: DiagnosticReporter + ): ExitStatus { reportDiagnostics(sortAndDeduplicateDiagnostics(diagnostics), reportDiagnostic); - reportEmittedFiles(emitOutput.emittedFiles, system); + reportEmittedFiles(emittedFiles, system); if (program.getCompilerOptions().listFiles) { forEach(program.getSourceFiles(), file => { @@ -131,7 +114,7 @@ namespace ts { }); } - if (emitOutput.emitSkipped && diagnostics.length > 0) { + if (emitSkipped && diagnostics.length > 0) { // If the emitter didn't emit anything, then pass that value along. return ExitStatus.DiagnosticsPresent_OutputsSkipped; } @@ -143,74 +126,6 @@ namespace ts { return ExitStatus.Success; } - function emitWatchedProgram(host: System, program: Program, builder: Builder) { - const emittedFiles: string[] = program.getCompilerOptions().listEmittedFiles ? [] : undefined; - let sourceMaps: SourceMapData[]; - let emitSkipped: boolean; - let diagnostics: Diagnostic[]; - - const result = builder.emitChangedFiles(program); - switch (result.length) { - case 0: - emitSkipped = true; - break; - case 1: - const emitOutput = result[0]; - ({ diagnostics, sourceMaps, emitSkipped } = emitOutput); - writeOutputFiles(emitOutput.outputFiles); - break; - default: - for (const emitOutput of result) { - if (emitOutput.emitSkipped) { - emitSkipped = true; - } - diagnostics = concatenate(diagnostics, emitOutput.diagnostics); - sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps); - writeOutputFiles(emitOutput.outputFiles); - } - } - - return { emitSkipped, diagnostics: diagnostics || [], emittedFiles, sourceMaps }; - - - function ensureDirectoriesExist(directoryPath: string) { - if (directoryPath.length > getRootLength(directoryPath) && !host.directoryExists(directoryPath)) { - const parentDirectory = getDirectoryPath(directoryPath); - ensureDirectoriesExist(parentDirectory); - host.createDirectory(directoryPath); - } - } - - function writeFile(fileName: string, data: string, writeByteOrderMark: boolean) { - try { - performance.mark("beforeIOWrite"); - ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); - - host.writeFile(fileName, data, writeByteOrderMark); - - performance.mark("afterIOWrite"); - performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); - } - catch (e) { - return createCompilerDiagnostic(Diagnostics.Could_not_write_file_0_Colon_1, fileName, e); - } - } - - function writeOutputFiles(outputFiles: OutputFile[]) { - if (outputFiles) { - for (const outputFile of outputFiles) { - const error = writeFile(outputFile.name, outputFile.text, outputFile.writeByteOrderMark); - if (error) { - diagnostics.push(error); - } - if (emittedFiles) { - emittedFiles.push(outputFile.name); - } - } - } - } - } - export function createWatchingSystemHost(pretty?: DiagnosticStyle, system = sys, parseConfigFile?: ParseConfigFile, reportDiagnostic?: DiagnosticReporter, reportWatchDiagnostic?: DiagnosticReporter @@ -228,7 +143,82 @@ namespace ts { }; function compileWatchedProgram(host: System, program: Program, builder: Builder) { - return compileProgram(system, program, () => emitWatchedProgram(host, program, builder), reportDiagnostic); + // First get and report any syntactic errors. + let diagnostics = program.getSyntacticDiagnostics(); + let reportSemanticDiagnostics = false; + + // If we didn't have any syntactic errors, then also try getting the global and + // semantic errors. + if (diagnostics.length === 0) { + diagnostics = program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics()); + + if (diagnostics.length === 0) { + reportSemanticDiagnostics = true; + } + } + + // Emit and report any errors we ran into. + const emittedFiles: string[] = program.getCompilerOptions().listEmittedFiles ? [] : undefined; + let sourceMaps: SourceMapData[]; + let emitSkipped: boolean; + + const result = builder.emitChangedFiles(program); + if (result.length === 0) { + emitSkipped = true; + } + else { + for (const emitOutput of result) { + if (emitOutput.emitSkipped) { + emitSkipped = true; + } + diagnostics = concatenate(diagnostics, emitOutput.diagnostics); + sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps); + writeOutputFiles(emitOutput.outputFiles); + } + } + + if (reportSemanticDiagnostics) { + diagnostics = diagnostics.concat(builder.getSemanticDiagnostics(program)); + } + return handleEmitOutputAndReportErrors(host, program, emittedFiles, emitSkipped, + diagnostics, reportDiagnostic); + + function ensureDirectoriesExist(directoryPath: string) { + if (directoryPath.length > getRootLength(directoryPath) && !host.directoryExists(directoryPath)) { + const parentDirectory = getDirectoryPath(directoryPath); + ensureDirectoriesExist(parentDirectory); + host.createDirectory(directoryPath); + } + } + + function writeFile(fileName: string, data: string, writeByteOrderMark: boolean) { + try { + performance.mark("beforeIOWrite"); + ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); + + host.writeFile(fileName, data, writeByteOrderMark); + + performance.mark("afterIOWrite"); + performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); + } + catch (e) { + return createCompilerDiagnostic(Diagnostics.Could_not_write_file_0_Colon_1, fileName, e); + } + } + + function writeOutputFiles(outputFiles: OutputFile[]) { + if (outputFiles) { + for (const outputFile of outputFiles) { + const error = writeFile(outputFile.name, outputFile.text, outputFile.writeByteOrderMark); + if (error) { + diagnostics.push(error); + } + if (emittedFiles) { + emittedFiles.push(outputFile.name); + } + } + } + } } }