From 2cbad6ab06ace3ff9b07d872ac4ee37eb7c0d306 Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 27 Mar 2018 17:02:04 -0700 Subject: [PATCH] Support completion details for string literal completions (#22664) --- src/services/completions.ts | 58 +++++++++++++------ .../completionForStringLiteral_details.ts | 28 +++++++++ 2 files changed, 67 insertions(+), 19 deletions(-) create mode 100644 tests/cases/fourslash/completionForStringLiteral_details.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index c30a302dc09..57e071efc1a 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -89,7 +89,7 @@ namespace ts.Completions { return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: true, entries }; } case StringLiteralCompletionKind.Types: { - const entries = completion.types.map(type => ({ name: type.value, kindModifiers: ScriptElementKindModifier.none, kind: ScriptElementKind.variableElement, sortText: "0" })); + const entries = completion.types.map(type => ({ name: type.value, kindModifiers: ScriptElementKindModifier.none, kind: ScriptElementKind.typeElement, sortText: "0" })); return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: true, entries }; } default: @@ -531,6 +531,15 @@ namespace ts.Completions { ): CompletionEntryDetails { const typeChecker = program.getTypeChecker(); const { name } = entryId; + + const contextToken = findPrecedingToken(position, sourceFile); + if (isInString(sourceFile, position, contextToken)) { + const stringLiteralCompletions = !contextToken || !isStringLiteralLike(contextToken) + ? undefined + : getStringLiteralCompletionEntries(sourceFile, contextToken, position, typeChecker, compilerOptions, host); + return stringLiteralCompletions && stringLiteralCompletionDetails(name, contextToken, stringLiteralCompletions, sourceFile, typeChecker); + } + // Compute all the completion symbols again. const symbolCompletion = getSymbolCompletionFromEntryId(typeChecker, log, compilerOptions, sourceFile, position, entryId, allSourceFiles); switch (symbolCompletion.type) { @@ -550,29 +559,40 @@ namespace ts.Completions { case "symbol": { const { symbol, location, symbolToOriginInfoMap, previousToken } = symbolCompletion; const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(symbolToOriginInfoMap, symbol, program, typeChecker, host, compilerOptions, sourceFile, previousToken, formatContext, getCanonicalFileName, allSourceFiles, preferences); - const kindModifiers = SymbolDisplay.getSymbolModifiers(symbol); - const { displayParts, documentation, symbolKind, tags } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, location, location, SemanticMeaning.All); - return { name, kindModifiers, kind: symbolKind, displayParts, documentation, tags, codeActions, source: sourceDisplay }; + return createCompletionDetailsForSymbol(symbol, typeChecker, sourceFile, location, codeActions, sourceDisplay); } - case "none": { + case "none": // Didn't find a symbol with this name. See if we can find a keyword instead. - if (allKeywordsCompletions().some(c => c.name === name)) { - return { - name, - kind: ScriptElementKind.keyword, - kindModifiers: ScriptElementKindModifier.none, - displayParts: [displayPart(name, SymbolDisplayPartKind.keyword)], - documentation: undefined, - tags: undefined, - codeActions: undefined, - source: undefined, - }; - } - return undefined; - } + return allKeywordsCompletions().some(c => c.name === name) ? createCompletionDetails(name, ScriptElementKindModifier.none, ScriptElementKind.keyword, [displayPart(name, SymbolDisplayPartKind.keyword)]) : undefined; } } + function createCompletionDetailsForSymbol(symbol: Symbol, checker: TypeChecker, sourceFile: SourceFile, location: Node, codeActions?: CodeAction[], sourceDisplay?: SymbolDisplayPart[]): CompletionEntryDetails { + const { displayParts, documentation, symbolKind, tags } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, sourceFile, location, location, SemanticMeaning.All); + return createCompletionDetails(symbol.name, SymbolDisplay.getSymbolModifiers(symbol), symbolKind, displayParts, documentation, tags, codeActions, sourceDisplay); + } + + function stringLiteralCompletionDetails(name: string, location: Node, completion: StringLiteralCompletion, sourceFile: SourceFile, checker: TypeChecker): CompletionEntryDetails | undefined { + switch (completion.kind) { + case StringLiteralCompletionKind.Paths: { + const match = find(completion.paths, p => p.name === name); + return match && createCompletionDetails(name, ScriptElementKindModifier.none, match.kind, [textPart(name)]); + } + case StringLiteralCompletionKind.Properties: { + const match = find(completion.symbols, s => s.name === name); + return match && createCompletionDetailsForSymbol(match, checker, sourceFile, location); + } + case StringLiteralCompletionKind.Types: + return find(completion.types, t => t.value === name) ? createCompletionDetails(name, ScriptElementKindModifier.none, ScriptElementKind.typeElement, [textPart(name)]) : undefined; + default: + return Debug.assertNever(completion); + } + } + + function createCompletionDetails(name: string, kindModifiers: string, kind: ScriptElementKind, displayParts: SymbolDisplayPart[], documentation?: SymbolDisplayPart[], tags?: JSDocTagInfo[], codeActions?: CodeAction[], source?: SymbolDisplayPart[]): CompletionEntryDetails { + return { name, kindModifiers, kind, displayParts, documentation, tags, codeActions, source }; + } + interface CodeActionsAndSourceDisplay { readonly codeActions: CodeAction[] | undefined; readonly sourceDisplay: SymbolDisplayPart[] | undefined; diff --git a/tests/cases/fourslash/completionForStringLiteral_details.ts b/tests/cases/fourslash/completionForStringLiteral_details.ts new file mode 100644 index 00000000000..46832bc9025 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteral_details.ts @@ -0,0 +1,28 @@ +/// + +// @Filename: /other.ts +////export const x = 0; + +// @Filename: /a.ts +////import {} from ".//*path*/"; +//// +////const x: "a" = "/*type*/"; +//// +////interface I { +//// /** Prop doc */ +//// x: number; +//// /** Method doc */ +//// m(): void; +////} +////declare const o: I; +////o["/*prop*/"]; + +goTo.marker("path"); +verify.completionListContains("other", "other", "", "script"); + +goTo.marker("type"); +verify.completionListContains("a", "a", "", "type"); + +goTo.marker("prop"); +verify.completionListContains("x", "(property) I.x: number", "Prop doc ", "property"); +verify.completionListContains("m", "(method) I.m(): void", "Method doc ", "method");