UNPKG

typescript-language-server

Version:

Language Server Protocol (LSP) implementation for TypeScript using tsserver

154 lines 6.31 kB
/*--------------------------------------------------------------------------------------------- * 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