diff --git a/src/server/protocol.ts b/src/server/protocol.ts index f8e94085c47..39a0045d97e 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -1839,7 +1839,7 @@ namespace ts.server.protocol { * begin with prefix. */ export interface CompletionsRequest extends FileLocationRequest { - command: CommandTypes.Completions; + command: CommandTypes.Completions | CommandTypes.CompletionInfo; arguments: CompletionsRequestArgs; } diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index 030b9286741..3c5b1d06eaf 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -168,8 +168,13 @@ namespace ts.codefix { // We sort the best codefixes first, so taking `first` is best for completions. const moduleSpecifier = first(getNewImportInfos(program, sourceFile, position, exportInfos, host, preferences)).moduleSpecifier; const fix = first(getFixForImport(exportInfos, symbolName, position, program, sourceFile, host, preferences)); - return { moduleSpecifier, codeAction: codeActionForFix({ host, formatContext }, sourceFile, symbolName, fix, getQuotePreference(sourceFile, preferences)) }; + return { moduleSpecifier, codeAction: codeFixActionToCodeAction(codeActionForFix({ host, formatContext }, sourceFile, symbolName, fix, getQuotePreference(sourceFile, preferences))) }; } + + function codeFixActionToCodeAction({ description, changes, commands }: CodeFixAction): CodeAction { + return { description, changes, commands }; + } + function getAllReExportingModules(exportedSymbol: Symbol, exportingModuleSymbol: Symbol, symbolName: string, sourceFile: SourceFile, checker: TypeChecker, allSourceFiles: ReadonlyArray): ReadonlyArray { const result: SymbolExportInfo[] = []; forEachExternalModule(checker, allSourceFiles, (moduleSymbol, moduleFile) => { diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index b99a2d9fe99..589668538c7 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -9210,6 +9210,128 @@ export function Test2() { }); }); + describe("tsserverProjectSystem completions", () => { + it("works", () => { + const aTs: File = { + path: "/a.ts", + content: "export const foo = 0;", + }; + const bTs: File = { + path: "/b.ts", + content: "foo", + }; + const tsconfig: File = { + path: "/tsconfig.json", + content: "{}", + }; + + const host = createServerHost([aTs, bTs, tsconfig]); + const session = createSession(host); + openFilesForSession([aTs, bTs], session); + + const requestLocation: protocol.FileLocationRequestArgs = { + file: bTs.path, + line: 1, + offset: 3, + }; + + const response = executeSessionRequest(session, protocol.CommandTypes.CompletionInfo, { + ...requestLocation, + includeExternalModuleExports: true, + prefix: "foo", + }); + const entry: protocol.CompletionEntry = { + hasAction: true, + insertText: undefined, + isRecommended: undefined, + kind: ScriptElementKind.constElement, + kindModifiers: ScriptElementKindModifier.exportedModifier, + name: "foo", + replacementSpan: undefined, + sortText: "0", + source: "/a", + }; + assert.deepEqual(response, { + isGlobalCompletion: true, + isMemberCompletion: false, + isNewIdentifierLocation: false, + entries: [entry], + }); + + const detailsRequestArgs: protocol.CompletionDetailsRequestArgs = { + ...requestLocation, + entryNames: [{ name: "foo", source: "/a" }], + }; + + const detailsResponse = executeSessionRequest(session, protocol.CommandTypes.CompletionDetails, detailsRequestArgs); + const detailsCommon: protocol.CompletionEntryDetails & CompletionEntryDetails = { + displayParts: [ + keywordPart(SyntaxKind.ConstKeyword), + spacePart(), + displayPart("foo", SymbolDisplayPartKind.localName), + punctuationPart(SyntaxKind.ColonToken), + spacePart(), + displayPart("0", SymbolDisplayPartKind.stringLiteral), + ], + documentation: emptyArray, + kind: ScriptElementKind.constElement, + kindModifiers: ScriptElementKindModifier.exportedModifier, + name: "foo", + source: [{ text: "./a", kind: "text" }], + tags: emptyArray, + }; + assert.deepEqual | undefined>(detailsResponse, [ + { + codeActions: [ + { + description: `Import 'foo' from module "./a"`, + changes: [ + { + fileName: "/b.ts", + textChanges: [ + { + start: { line: 1, offset: 1 }, + end: { line: 1, offset: 1 }, + newText: 'import { foo } from "./a";\n\n', + }, + ], + }, + ], + commands: undefined, + }, + ], + ...detailsCommon, + }, + ]); + + interface CompletionDetailsFullRequest extends protocol.FileLocationRequest { + readonly command: protocol.CommandTypes.CompletionDetailsFull; + readonly arguments: protocol.CompletionDetailsRequestArgs; + } + interface CompletionDetailsFullResponse extends protocol.Response { + readonly body?: ReadonlyArray; + } + const detailsFullResponse = executeSessionRequest(session, protocol.CommandTypes.CompletionDetailsFull, detailsRequestArgs); + assert.deepEqual | undefined>(detailsFullResponse, [ + { + codeActions: [ + { + description: `Import 'foo' from module "./a"`, + changes: [ + { + fileName: "/b.ts", + textChanges: [createTextChange(createTextSpan(0, 0), 'import { foo } from "./a";\n\n')], + }, + ], + commands: undefined, + } + ], + ...detailsCommon, + } + ]); + }); + }); + describe("tsserverProjectSystem project references", () => { const aTs: File = { path: "/a/a.ts", diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 5e992f3847a..584557e8b0f 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -6948,7 +6948,7 @@ declare namespace ts.server.protocol { * begin with prefix. */ interface CompletionsRequest extends FileLocationRequest { - command: CommandTypes.Completions; + command: CommandTypes.Completions | CommandTypes.CompletionInfo; arguments: CompletionsRequestArgs; } /**