Adds a CodeAction provider to support fixits

This commit is contained in:
Orta Therox 2019-08-29 13:16:22 -04:00
parent 2389ae38aa
commit b8063f957f
5 changed files with 87 additions and 5 deletions

View file

@ -160,13 +160,16 @@ export class DiagnostcsAdapter extends Adapter {
return null; return null;
} }
const promises: Promise<ts.Diagnostic[]>[] = []; const promises: Promise<ts.Diagnostic[]>[] = [];
const { noSyntaxValidation, noSemanticValidation } = this._defaults.getDiagnosticsOptions(); const { noSyntaxValidation, noSemanticValidation, noSuggestionDiagnostics } = this._defaults.getDiagnosticsOptions();
if (!noSyntaxValidation) { if (!noSyntaxValidation) {
promises.push(worker.getSyntacticDiagnostics(resource.toString())); promises.push(worker.getSyntacticDiagnostics(resource.toString()));
} }
if (!noSemanticValidation) { if (!noSemanticValidation) {
promises.push(worker.getSemanticDiagnostics(resource.toString())); promises.push(worker.getSemanticDiagnostics(resource.toString()));
} }
if (!noSuggestionDiagnostics) {
promises.push(worker.getSuggestionDiagnostics(resource.toString()));
}
return Promise.all(promises); return Promise.all(promises);
}).then(diagnostics => { }).then(diagnostics => {
if (!diagnostics || !monaco.editor.getModel(resource)) { if (!diagnostics || !monaco.editor.getModel(resource)) {
@ -188,14 +191,24 @@ export class DiagnostcsAdapter extends Adapter {
const { lineNumber: endLineNumber, column: endColumn } = this._offsetToPosition(resource, diag.start + diag.length); const { lineNumber: endLineNumber, column: endColumn } = this._offsetToPosition(resource, diag.start + diag.length);
return { return {
severity: monaco.MarkerSeverity.Error, severity: this._tsDiagnosticCategoryToMarkerSeverity(diag.category),
startLineNumber, startLineNumber,
startColumn, startColumn,
endLineNumber, endLineNumber,
endColumn, endColumn,
message: flattenDiagnosticMessageText(diag.messageText, '\n') message: flattenDiagnosticMessageText(diag.messageText, '\n'),
code: diag.code.toString()
}; };
} }
private _tsDiagnosticCategoryToMarkerSeverity(category: ts.DiagnosticCategory): monaco.MarkerSeverity {
switch(category) {
case ts.DiagnosticCategory.Error: return monaco.MarkerSeverity.Error
case ts.DiagnosticCategory.Message: return monaco.MarkerSeverity.Info
case ts.DiagnosticCategory.Warning: return monaco.MarkerSeverity.Warning
case ts.DiagnosticCategory.Suggestion: return monaco.MarkerSeverity.Hint
}
}
} }
// --- suggest ------ // --- suggest ------
@ -626,3 +639,57 @@ export class FormatOnTypeAdapter extends FormatHelper implements monaco.language
}); });
} }
} }
// --- code actions ------
export class CodeActionAdaptor extends FormatHelper implements monaco.languages.CodeActionProvider {
public provideCodeActions(model: monaco.editor.ITextModel, range: Range, context: monaco.languages.CodeActionContext, token: CancellationToken): Promise<(monaco.languages.Command | monaco.languages.CodeAction)[]> {
const resource = model.uri;
return this._worker(resource).then(worker => {
const start = this._positionToOffset(resource, { lineNumber: range.startLineNumber, column: range.startColumn });
const end = this._positionToOffset(resource, { lineNumber: range.endLineNumber, column: range.endColumn });
// TODO: where to get the current formatting options from?
const formatOptions = FormatHelper._convertOptions({insertSpaces: true, tabSize: 2});
const errorCodes = context.markers.filter(m => m.code).map(m => m.code).map(Number);
return worker.getCodeFixesAtPosition(resource.toString(), start, end, errorCodes, formatOptions);
}).then(codeFixes => {
return codeFixes.filter(fix => {
// Removes any 'make a new file'-type code fix
return fix.changes.filter(change => change.isNewFile).length === 0;
}).map(fix => {
return this._tsCodeFixActionToMonacoCodeAction(model, context, fix);
})
});
}
private _tsCodeFixActionToMonacoCodeAction(model: monaco.editor.ITextModel, context: monaco.languages.CodeActionContext, codeFix: ts.CodeFixAction): monaco.languages.CodeAction {
const edits: monaco.languages.ResourceTextEdit[] = codeFix.changes.map(edit => ({
resource: model.uri,
edits: edit.textChanges.map(tc => ({
range: this._textSpanToRange(model.uri, tc.span),
text: tc.newText
}))
}));
const action: monaco.languages.CodeAction = {
title: codeFix.description,
edit: { edits: edits },
diagnostics: context.markers,
command: {
id: codeFix.fixName,
title: codeFix.description,
tooltip: codeFix.description
},
kind: codeFix.fixName
};
return action;
}
}

1
src/monaco.d.ts vendored
View file

@ -125,6 +125,7 @@ declare module monaco.languages.typescript {
export interface DiagnosticsOptions { export interface DiagnosticsOptions {
noSemanticValidation?: boolean; noSemanticValidation?: boolean;
noSyntaxValidation?: boolean; noSyntaxValidation?: boolean;
noSuggestionDiagnostics ?: boolean;
} }
export interface LanguageServiceDefaults { export interface LanguageServiceDefaults {

View file

@ -64,6 +64,8 @@ function setupMode(defaults: LanguageServiceDefaultsImpl, modeId: string): (firs
monaco.languages.registerDocumentSymbolProvider(modeId, new languageFeatures.OutlineAdapter(worker)); monaco.languages.registerDocumentSymbolProvider(modeId, new languageFeatures.OutlineAdapter(worker));
monaco.languages.registerDocumentRangeFormattingEditProvider(modeId, new languageFeatures.FormatAdapter(worker)); monaco.languages.registerDocumentRangeFormattingEditProvider(modeId, new languageFeatures.FormatAdapter(worker));
monaco.languages.registerOnTypeFormattingEditProvider(modeId, new languageFeatures.FormatOnTypeAdapter(worker)); monaco.languages.registerOnTypeFormattingEditProvider(modeId, new languageFeatures.FormatOnTypeAdapter(worker));
monaco.languages.registerCodeActionProvider(modeId, new languageFeatures.CodeActionAdaptor(worker));
new languageFeatures.DiagnostcsAdapter(defaults, modeId, worker); new languageFeatures.DiagnostcsAdapter(defaults, modeId, worker);
return worker; return worker;

View file

@ -146,6 +146,12 @@ export class TypeScriptWorker implements ts.LanguageServiceHost {
return Promise.resolve(diagnostics); return Promise.resolve(diagnostics);
} }
getSuggestionDiagnostics(fileName: string): Promise<ts.DiagnosticWithLocation[]> {
const diagnostics = this._languageService.getSuggestionDiagnostics(fileName);
TypeScriptWorker.clearFiles(diagnostics);
return Promise.resolve(diagnostics);
}
getCompilerOptionsDiagnostics(fileName: string): Promise<ts.Diagnostic[]> { getCompilerOptionsDiagnostics(fileName: string): Promise<ts.Diagnostic[]> {
const diagnostics = this._languageService.getCompilerOptionsDiagnostics(); const diagnostics = this._languageService.getCompilerOptionsDiagnostics();
TypeScriptWorker.clearFiles(diagnostics); TypeScriptWorker.clearFiles(diagnostics);
@ -200,6 +206,11 @@ export class TypeScriptWorker implements ts.LanguageServiceHost {
return Promise.resolve(this._languageService.getEmitOutput(fileName)); return Promise.resolve(this._languageService.getEmitOutput(fileName));
} }
getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes:number[], formatOptions: ts.FormatCodeOptions): Promise<ReadonlyArray<ts.CodeFixAction>> {
const preferences = {}
return Promise.resolve(this._languageService.getCodeFixesAtPosition(fileName, start, end, errorCodes, formatOptions, preferences));
}
updateExtraLibs(extraLibs: IExtraLibs) { updateExtraLibs(extraLibs: IExtraLibs) {
this._extraLibs = extraLibs; this._extraLibs = extraLibs;
} }

View file

@ -165,10 +165,11 @@
'var game = new Conway.GameOfLife();', 'var game = new Conway.GameOfLife();',
].join('\n'), ].join('\n'),
language: 'typescript' language: 'typescript',
lightbulb: { enabled: true }
}); });
}); });
</script> </script>
</body> </body>
</html> </html>