diff --git a/package.json b/package.json index c044c2b..799ca8e 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,11 @@ "title": "Run `tsc --watch` for current project in terminal buffer.", "category": "TSServer", "command": "tsserver.watchBuild" + }, + { + "title": "Fix autofixable problems of current document.", + "category": "TSServer", + "command": "tsserver.executeAutofix" } ], "configuration": { diff --git a/src/index.ts b/src/index.ts index c5f487e..dcacd41 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import { commands, ExtensionContext, services, workspace } from 'coc.nvim' import TsserverService from './server' -import { Command, OpenTsServerLogCommand, ReloadProjectsCommand, TypeScriptGoToProjectConfigCommand } from './server/commands' +import { Command, OpenTsServerLogCommand, AutoFixCommand, ReloadProjectsCommand, TypeScriptGoToProjectConfigCommand } from './server/commands' import OrganizeImportsCommand from './server/organizeImports' export async function activate(context: ExtensionContext): Promise<void> { @@ -22,6 +22,7 @@ export async function activate(context: ExtensionContext): Promise<void> { subscriptions.push(commands.registerCommand(id as string, execute, cmd)) } + registCommand(new AutoFixCommand(service.clientHost)) registCommand(new ReloadProjectsCommand(service.clientHost)) registCommand(new OpenTsServerLogCommand(service.clientHost)) registCommand(new TypeScriptGoToProjectConfigCommand(service.clientHost)) diff --git a/src/server/commands.ts b/src/server/commands.ts index 2bd684d..955dda8 100644 --- a/src/server/commands.ts +++ b/src/server/commands.ts @@ -1,8 +1,10 @@ +import { diagnosticManager, workspace } from 'coc.nvim' import { CancellationToken } from 'vscode-languageserver-protocol' import URI from 'vscode-uri' -import { workspace } from 'coc.nvim' -import { ProjectInfoResponse } from './protocol' +import * as Proto from './protocol' import TypeScriptServiceClientHost from './typescriptServiceClientHost' +import * as typeConverters from './utils/typeConverters' +import { WorkspaceEdit, TextEdit } from 'vscode-languageserver-types' export interface Command { readonly id: string | string[] @@ -54,7 +56,7 @@ async function goToProjectConfig(clientHost: TypeScriptServiceClientHost, uri: s } const client = clientHost.serviceClient const file = client.toPath(uri) - let res: ProjectInfoResponse | undefined + let res: Proto.ProjectInfoResponse | undefined try { res = await client.execute('projectInfo', { file, needFileNameList: false }, CancellationToken.None) } catch { @@ -77,3 +79,54 @@ async function goToProjectConfig(clientHost: TypeScriptServiceClientHost, uri: s function isImplicitProjectConfigFile(configFileName: string): boolean { return configFileName.indexOf('/dev/null/') === 0 } + +const autoFixableDiagnosticCodes = new Set<number>([ + 2420, // Incorrectly implemented interface + 2552, // Cannot find name + 2304, // Cannot find name +]) + +export class AutoFixCommand implements Command { + public readonly id = 'tsserver.executeAutofix' + + constructor(private client: TypeScriptServiceClientHost) { + } + + public async execute(): Promise<void> { + let document = await workspace.document + if (!this.client.handles(document.uri)) { + workspace.showMessage('Document is not handled by tsserver.', 'warning') + return + } + let file = this.client.serviceClient.toPath(document.uri) + let diagnostics = diagnosticManager.getDiagnostics(document.uri) + diagnostics = diagnostics.filter(x => autoFixableDiagnosticCodes.has(x.code as number)) + if (diagnostics.length == 0) { + workspace.showMessage('No autofixable diagnostics found', 'warning') + } + let client = this.client.serviceClient + let edits: TextEdit[] = [] + for (let diagnostic of diagnostics) { + const args: Proto.CodeFixRequestArgs = { + ...typeConverters.Range.toFileRangeRequestArgs(file, diagnostic.range), + errorCodes: [+(diagnostic.code!)] + } + const response: Proto.GetCodeFixesResponse = await client.execute('getCodeFixes', args) + if (response.type !== 'response' || !response.body || response.body.length < 1) { + continue + } + const fix = response.body[0] + for (let change of fix.changes) { + if (change.fileName != file) continue + // change.fileName + for (let ch of change.textChanges) { + edits.push({ + range: typeConverters.Range.fromTextSpan(ch), + newText: ch.newText + }) + } + } + } + if (edits.length) await document.applyEdits(workspace.nvim, edits) + } +} diff --git a/src/server/typescriptServiceClient.ts b/src/server/typescriptServiceClient.ts index 1761df5..e9a8e1c 100644 --- a/src/server/typescriptServiceClient.ts +++ b/src/server/typescriptServiceClient.ts @@ -815,7 +815,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient public getProjectRootPath(uri: string): string { let u = Uri.parse(uri) if (u.scheme != 'file') return workspace.root - let res = findUp.sync(['tsconfig.json', 'jsconfig.json', 'package.json'], { cwd: path.dirname(u.fsPath) }) + let res = findUp.sync(['package.json', 'tsconfig.json', 'jsconfig.json'], { cwd: path.dirname(u.fsPath) }) return res ? path.dirname(res) : workspace.rootPath } }