diff --git a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts index 32326817c..a3e1b5e1d 100644 --- a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts @@ -27,6 +27,11 @@ import { changeSvelteComponentName, convertRange } from '../utils'; import { CompletionsProviderImpl } from './CompletionProvider'; import { findContainingNode, isNoTextSpanInGeneratedCode, SnapshotFragmentMap } from './utils'; +/** + * TODO change this to protocol constant if it's part of the protocol + */ +export const SORT_IMPORT_CODE_ACTION_KIND = 'source.sortImports'; + interface RefactorArgs { type: 'refactor'; refactorName: string; @@ -51,6 +56,27 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { return await this.organizeImports(document, cancellationToken); } + if (context.only?.[0] === SORT_IMPORT_CODE_ACTION_KIND) { + return await this.organizeImports( + document, + cancellationToken, + /**skipDestructiveCodeActions */ true + ); + } + + // for source action command (all source.xxx) + // vscode would show different source code action kinds to choose from + if (context.only?.[0] === CodeActionKind.Source) { + return [ + ...(await this.organizeImports(document, cancellationToken)), + ...(await this.organizeImports( + document, + cancellationToken, + /**skipDestructiveCodeActions */ true + )) + ]; + } + if ( context.diagnostics.length && (!context.only || context.only.includes(CodeActionKind.QuickFix)) @@ -67,7 +93,8 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { private async organizeImports( document: Document, - cancellationToken: CancellationToken | undefined + cancellationToken: CancellationToken | undefined, + skipDestructiveCodeActions = false ): Promise { if (!document.scriptInfo && !document.moduleScriptInfo) { return []; @@ -92,7 +119,8 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { const changes = lang.organizeImports( { fileName: tsDoc.filePath, - type: 'file' + type: 'file', + skipDestructiveCodeActions }, { semicolons: useSemicolons @@ -125,9 +153,11 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { return [ CodeAction.create( - 'Organize Imports', + skipDestructiveCodeActions ? 'Sort Imports' : 'Organize Imports', { documentChanges }, - CodeActionKind.SourceOrganizeImports + skipDestructiveCodeActions + ? SORT_IMPORT_CODE_ACTION_KIND + : CodeActionKind.SourceOrganizeImports ) ]; } diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts index acfe572db..6caedd24c 100644 --- a/packages/language-server/src/server.ts +++ b/packages/language-server/src/server.ts @@ -37,6 +37,7 @@ import { debounceThrottle, isNotNullOrUndefined, normalizeUri, urlToPath } from import { FallbackWatcher } from './lib/FallbackWatcher'; import { configLoader } from './lib/documents/configLoader'; import { setIsTrusted } from './importPackage'; +import { SORT_IMPORT_CODE_ACTION_KIND } from './plugins/typescript/features/CodeActionsProvider'; namespace TagCloseRequest { export const type: RequestType = @@ -211,6 +212,7 @@ export function startServer(options?: LSOptions) { codeActionKinds: [ CodeActionKind.QuickFix, CodeActionKind.SourceOrganizeImports, + SORT_IMPORT_CODE_ACTION_KIND, ...(clientSupportApplyEditCommand ? [CodeActionKind.Refactor] : []) ] } diff --git a/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts b/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts index c2cb1bb9c..e4cc0864c 100644 --- a/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts @@ -11,7 +11,10 @@ import { } from 'vscode-languageserver'; import { Document, DocumentManager } from '../../../../src/lib/documents'; import { LSConfigManager } from '../../../../src/ls-config'; -import { CodeActionsProviderImpl } from '../../../../src/plugins/typescript/features/CodeActionsProvider'; +import { + CodeActionsProviderImpl, + SORT_IMPORT_CODE_ACTION_KIND +} from '../../../../src/plugins/typescript/features/CodeActionsProvider'; import { CompletionsProviderImpl } from '../../../../src/plugins/typescript/features/CompletionProvider'; import { LSAndTSDocResolver } from '../../../../src/plugins/typescript/LSAndTSDocResolver'; import { __resetCache } from '../../../../src/plugins/typescript/service'; @@ -426,6 +429,98 @@ function test(useNewTransformation: boolean) { ]); }); + it('sort imports', async () => { + const { provider, document } = setup('codeactions.svelte'); + + const codeActions = await provider.getCodeActions( + document, + Range.create(Position.create(1, 4), Position.create(1, 5)), + { + diagnostics: [], + only: [SORT_IMPORT_CODE_ACTION_KIND] + } + ); + (codeActions[0]?.edit?.documentChanges?.[0])?.edits.forEach( + (edit) => (edit.newText = harmonizeNewLines(edit.newText)) + ); + + assert.deepStrictEqual(codeActions, [ + { + edit: { + documentChanges: [ + { + edits: [ + { + // eslint-disable-next-line max-len + newText: + "import { A,B } from 'bla';\n" + + "import { C } from 'blubb';\n" + + "import { D } from 'd';\n", + + range: { + start: { + character: 0, + line: 1 + }, + end: { + character: 0, + line: 2 + } + } + }, + { + newText: '', + range: { + start: { + character: 0, + line: 2 + }, + end: { + character: 0, + line: 3 + } + } + }, + { + newText: '', + range: { + start: { + character: 0, + line: 3 + }, + end: { + character: 0, + line: 4 + } + } + }, + { + newText: '', + range: { + start: { + character: 0, + line: 4 + }, + end: { + character: 0, + line: 5 + } + } + } + ], + textDocument: { + uri: getUri('codeactions.svelte'), + version: null + } + } + ] + }, + kind: SORT_IMPORT_CODE_ACTION_KIND, + title: 'Sort Imports' + } + ]); + }); + it('organizes imports with module script', async () => { const { provider, document } = setup('organize-imports-with-module.svelte');