Get semantic diagnostics for the program from builder so that it caches the errors of unchanged files

This commit is contained in:
Sheetal Nandi
2017-08-07 14:47:32 -07:00
parent 7474ba762c
commit 6385f7e3bb
4 changed files with 160 additions and 98 deletions

View File

@@ -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<FileInfo>;
const semanticDiagnosticsPerFile = createMap<Diagnostic[]>();
let changedFilesSinceLastEmit: Map<true>;
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<true>();
@@ -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);
}

View File

@@ -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<true>())).set(path, true);
}

View File

@@ -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();

View File

@@ -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);
}
}
}
}
}
}