typescript-language-server
Version:
Language Server Protocol (LSP) implementation for TypeScript using tsserver
154 lines • 6.31 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as lsp from 'vscode-languageserver';
import { toTextDocumentEdit } from '../protocol-translation.js';
import * as errorCodes from '../utils/errorCodes.js';
import * as fixNames from '../utils/fixNames.js';
import { CodeActionKind } from '../utils/types.js';
import { Range } from '../utils/typeConverters.js';
async function buildIndividualFixes(fixes, client, file, documents, diagnostics) {
const edits = [];
for (const diagnostic of diagnostics) {
for (const { codes, fixName } of fixes) {
if (!codes.has(diagnostic.code)) {
continue;
}
const args = {
...Range.toFileRangeRequestArgs(file, diagnostic.range),
errorCodes: [+diagnostic.code],
};
const response = await client.request("getCodeFixes" /* CommandTypes.GetCodeFixes */, args);
if (response.type !== 'response') {
continue;
}
const fix = response.body?.find(fix => fix.fixName === fixName);
if (fix) {
edits.push(...fix.changes.map(change => toTextDocumentEdit(change, documents)));
break;
}
}
}
return edits;
}
async function buildCombinedFix(fixes, client, file, documents, diagnostics) {
const edits = [];
for (const diagnostic of diagnostics) {
for (const { codes, fixName } of fixes) {
if (!codes.has(diagnostic.code)) {
continue;
}
const args = {
...Range.toFileRangeRequestArgs(file, diagnostic.range),
errorCodes: [+diagnostic.code],
};
const response = await client.request("getCodeFixes" /* CommandTypes.GetCodeFixes */, args);
if (response.type !== 'response' || !response.body?.length) {
continue;
}
const fix = response.body?.find(fix => fix.fixName === fixName);
if (!fix) {
continue;
}
if (!fix.fixId) {
edits.push(...fix.changes.map(change => toTextDocumentEdit(change, documents)));
return edits;
}
const combinedArgs = {
scope: {
type: 'file',
args: { file },
},
fixId: fix.fixId,
};
const combinedResponse = await client.request("getCombinedCodeFix" /* CommandTypes.GetCombinedCodeFix */, combinedArgs);
if (combinedResponse.type !== 'response' || !combinedResponse.body) {
return edits;
}
edits.push(...combinedResponse.body.changes.map(change => toTextDocumentEdit(change, documents)));
return edits;
}
}
return edits;
}
// #region Source Actions
class SourceAction {
}
class SourceFixAll extends SourceAction {
constructor() {
super(...arguments);
this.title = 'Fix all';
}
async build(client, file, documents, diagnostics) {
const edits = [];
edits.push(...await buildIndividualFixes([
{ codes: errorCodes.incorrectlyImplementsInterface, fixName: fixNames.classIncorrectlyImplementsInterface },
{ codes: errorCodes.asyncOnlyAllowedInAsyncFunctions, fixName: fixNames.awaitInSyncFunction },
], client, file, documents, diagnostics));
edits.push(...await buildCombinedFix([
{ codes: errorCodes.unreachableCode, fixName: fixNames.unreachableCode },
], client, file, documents, diagnostics));
if (!edits.length) {
return null;
}
return lsp.CodeAction.create(this.title, { documentChanges: edits }, SourceFixAll.kind.value);
}
}
SourceFixAll.kind = CodeActionKind.SourceFixAllTs;
class SourceRemoveUnused extends SourceAction {
constructor() {
super(...arguments);
this.title = 'Remove all unused code';
}
async build(client, file, documents, diagnostics) {
const edits = await buildCombinedFix([
{ codes: errorCodes.variableDeclaredButNeverUsed, fixName: fixNames.unusedIdentifier },
], client, file, documents, diagnostics);
if (!edits.length) {
return null;
}
return lsp.CodeAction.create(this.title, { documentChanges: edits }, SourceRemoveUnused.kind.value);
}
}
SourceRemoveUnused.kind = CodeActionKind.SourceRemoveUnusedTs;
class SourceAddMissingImports extends SourceAction {
constructor() {
super(...arguments);
this.title = 'Add all missing imports';
}
async build(client, file, documents, diagnostics) {
const edits = await buildCombinedFix([
{ codes: errorCodes.cannotFindName, fixName: fixNames.fixImport },
], client, file, documents, diagnostics);
if (!edits.length) {
return null;
}
return lsp.CodeAction.create(this.title, { documentChanges: edits }, SourceAddMissingImports.kind.value);
}
}
SourceAddMissingImports.kind = CodeActionKind.SourceAddMissingImportsTs;
//#endregion
export class TypeScriptAutoFixProvider {
constructor(client) {
this.client = client;
}
static get kinds() {
return TypeScriptAutoFixProvider.kindProviders.map(provider => provider.kind);
}
async provideCodeActions(kinds, file, diagnostics, documents) {
const results = [];
for (const provider of TypeScriptAutoFixProvider.kindProviders) {
if (kinds.some(kind => kind.contains(provider.kind))) {
results.push((new provider).build(this.client, file, documents, diagnostics));
}
}
return (await Promise.all(results)).flatMap(result => result || []);
}
}
TypeScriptAutoFixProvider.kindProviders = [
SourceFixAll,
SourceRemoveUnused,
SourceAddMissingImports,
];
//# sourceMappingURL=fix-all.js.map