Allow rich response for compile on save (#37462)

Fixes #30739
This commit is contained in:
Sheetal Nandi
2020-03-19 14:03:21 -07:00
committed by GitHub
parent c513a4adea
commit 7e07a2b5d1
11 changed files with 177 additions and 26 deletions

View File

@@ -118,6 +118,12 @@ namespace ts.server {
return (watch as GeneratedFileWatcher).generatedFilePath !== undefined;
}
/*@internal*/
export interface EmitResult {
emitSkipped: boolean;
diagnostics: readonly Diagnostic[];
}
export abstract class Project implements LanguageServiceHost, ModuleResolutionHost {
private rootFiles: ScriptInfo[] = [];
private rootFilesMap = createMap<ProjectRootFile>();
@@ -587,11 +593,11 @@ namespace ts.server {
/**
* Returns true if emit was conducted
*/
emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean {
emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): EmitResult {
if (!this.languageServiceEnabled || !this.shouldEmitFile(scriptInfo)) {
return false;
return { emitSkipped: true, diagnostics: emptyArray };
}
const { emitSkipped, outputFiles } = this.getLanguageService(/*ensureSynchronized*/ false).getEmitOutput(scriptInfo.fileName);
const { emitSkipped, diagnostics, outputFiles } = this.getLanguageService().getEmitOutput(scriptInfo.fileName);
if (!emitSkipped) {
for (const outputFile of outputFiles) {
const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, this.currentDirectory);
@@ -599,7 +605,7 @@ namespace ts.server {
}
}
return !emitSkipped;
return { emitSkipped, diagnostics };
}
enableLanguageService() {

View File

@@ -857,10 +857,25 @@ namespace ts.server.protocol {
}
/** @internal */
export interface EmitOutputRequest extends FileRequest {}
export interface EmitOutputRequest extends FileRequest {
command: CommandTypes.EmitOutput;
arguments: EmitOutputRequestArgs;
}
/** @internal */
export interface EmitOutputRequestArgs extends FileRequestArgs {
includeLinePosition?: boolean;
/** if true - return response as object with emitSkipped and diagnostics */
richResponse?: boolean;
}
/** @internal */
export interface EmitOutputResponse extends Response {
readonly body: EmitOutput;
readonly body: EmitOutput | ts.EmitOutput;
}
/** @internal */
export interface EmitOutput {
outputFiles: OutputFile[];
emitSkipped: boolean;
diagnostics: Diagnostic[] | DiagnosticWithLinePosition[];
}
/**
@@ -1808,6 +1823,18 @@ namespace ts.server.protocol {
* if true - then file should be recompiled even if it does not have any changes.
*/
forced?: boolean;
includeLinePosition?: boolean;
/** if true - return response as object with emitSkipped and diagnostics */
richResponse?: boolean;
}
export interface CompileOnSaveEmitFileResponse extends Response {
body: boolean | EmitResult;
}
export interface EmitResult {
emitSkipped: boolean;
diagnostics: Diagnostic[] | DiagnosticWithLinePosition[];
}
/**

View File

@@ -85,9 +85,9 @@ namespace ts.server {
return { line: lineAndCharacter.line + 1, offset: lineAndCharacter.character + 1 };
}
function formatConfigFileDiag(diag: Diagnostic, includeFileName: true): protocol.DiagnosticWithFileName;
function formatConfigFileDiag(diag: Diagnostic, includeFileName: false): protocol.Diagnostic;
function formatConfigFileDiag(diag: Diagnostic, includeFileName: boolean): protocol.Diagnostic | protocol.DiagnosticWithFileName {
function formatDiagnosticToProtocol(diag: Diagnostic, includeFileName: true): protocol.DiagnosticWithFileName;
function formatDiagnosticToProtocol(diag: Diagnostic, includeFileName: false): protocol.Diagnostic;
function formatDiagnosticToProtocol(diag: Diagnostic, includeFileName: boolean): protocol.Diagnostic | protocol.DiagnosticWithFileName {
const start = (diag.file && convertToLocation(getLineAndCharacterOfPosition(diag.file, diag.start!)))!; // TODO: GH#18217
const end = (diag.file && convertToLocation(getLineAndCharacterOfPosition(diag.file, diag.start! + diag.length!)))!; // TODO: GH#18217
const text = flattenDiagnosticMessageText(diag.messageText, "\n");
@@ -699,7 +699,7 @@ namespace ts.server {
break;
case ConfigFileDiagEvent:
const { triggerFile, configFileName: configFile, diagnostics } = event.data;
const bakedDiags = map(diagnostics, diagnostic => formatConfigFileDiag(diagnostic, /*includeFileName*/ true));
const bakedDiags = map(diagnostics, diagnostic => formatDiagnosticToProtocol(diagnostic, /*includeFileName*/ true));
this.event<protocol.ConfigFileDiagnosticEventBody>({
triggerFile,
configFile,
@@ -998,7 +998,7 @@ namespace ts.server {
this.convertToDiagnosticsWithLinePositionFromDiagnosticFile(diagnosticsForConfigFile) :
map(
diagnosticsForConfigFile,
diagnostic => formatConfigFileDiag(diagnostic, /*includeFileName*/ false)
diagnostic => formatDiagnosticToProtocol(diagnostic, /*includeFileName*/ false)
);
}
@@ -1009,8 +1009,10 @@ namespace ts.server {
length: d.length!, // TODO: GH#18217
category: diagnosticCategoryName(d),
code: d.code,
source: d.source,
startLocation: (d.file && convertToLocation(getLineAndCharacterOfPosition(d.file, d.start!)))!, // TODO: GH#18217
endLocation: (d.file && convertToLocation(getLineAndCharacterOfPosition(d.file, d.start! + d.length!)))!, // TODO: GH#18217
reportsUnnecessary: d.reportsUnnecessary,
relatedInformation: map(d.relatedInformation, formatRelatedInformation)
}));
}
@@ -1108,11 +1110,20 @@ namespace ts.server {
};
}
private getEmitOutput(args: protocol.FileRequestArgs): EmitOutput {
private getEmitOutput(args: protocol.EmitOutputRequestArgs): EmitOutput | protocol.EmitOutput {
const { file, project } = this.getFileAndProject(args);
return project.shouldEmitFile(project.getScriptInfo(file)) ?
project.getLanguageService().getEmitOutput(file) :
{ emitSkipped: true, outputFiles: [] };
if (!project.shouldEmitFile(project.getScriptInfo(file))) {
return { emitSkipped: true, outputFiles: [], diagnostics: [] };
}
const result = project.getLanguageService().getEmitOutput(file);
return args.richResponse ?
{
...result,
diagnostics: args.includeLinePosition ?
this.convertToDiagnosticsWithLinePositionFromDiagnosticFile(result.diagnostics) :
result.diagnostics.map(d => formatDiagnosticToProtocol(d, /*includeFileName*/ true))
} :
result;
}
private mapDefinitionInfo(definitions: readonly DefinitionInfo[], project: Project): readonly protocol.FileSpanWithContext[] {
@@ -1708,16 +1719,24 @@ namespace ts.server {
);
}
private emitFile(args: protocol.CompileOnSaveEmitFileRequestArgs) {
private emitFile(args: protocol.CompileOnSaveEmitFileRequestArgs): boolean | protocol.EmitResult | EmitResult {
const { file, project } = this.getFileAndProject(args);
if (!project) {
Errors.ThrowNoProject();
}
if (!project.languageServiceEnabled) {
return false;
return args.richResponse ? { emitSkipped: true, diagnostics: [] } : false;
}
const scriptInfo = project.getScriptInfo(file)!;
return project.emitFile(scriptInfo, (path, data, writeByteOrderMark) => this.host.writeFile(path, data, writeByteOrderMark));
const { emitSkipped, diagnostics } = project.emitFile(scriptInfo, (path, data, writeByteOrderMark) => this.host.writeFile(path, data, writeByteOrderMark));
return args.richResponse ?
{
emitSkipped,
diagnostics: args.includeLinePosition ?
this.convertToDiagnosticsWithLinePositionFromDiagnosticFile(diagnostics) :
diagnostics.map(d => formatDiagnosticToProtocol(d, /*includeFileName*/ true))
} :
!emitSkipped;
}
private getSignatureHelpItems(args: protocol.SignatureHelpRequestArgs, simplifiedResult: boolean): protocol.SignatureHelpItems | SignatureHelpItems | undefined {