Skip to content

Commit 9c96eee

Browse files
author
Andy
authored
Support completion details for special JsDoc completions (#19494)
1 parent 9615e54 commit 9c96eee

File tree

3 files changed

+84
-30
lines changed

3 files changed

+84
-30
lines changed

src/services/completions.ts

Lines changed: 49 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -361,21 +361,25 @@ namespace ts.Completions {
361361
position: number,
362362
{ name, source }: CompletionEntryIdentifier,
363363
allSourceFiles: ReadonlyArray<SourceFile>,
364-
): { symbol: Symbol, location: Node, symbolToOriginInfoMap: SymbolOriginInfoMap } | undefined {
364+
): { type: "symbol", symbol: Symbol, location: Node, symbolToOriginInfoMap: SymbolOriginInfoMap } | { type: "request", request: Request } | { type: "none" } {
365365
const completionData = getCompletionData(typeChecker, log, sourceFile, position, allSourceFiles);
366366
if (!completionData) {
367-
return undefined;
367+
return { type: "none" };
368+
}
369+
370+
const { symbols, location, allowStringLiteral, symbolToOriginInfoMap, request } = completionData;
371+
if (request) {
372+
return { type: "request", request };
368373
}
369374

370-
const { symbols, location, allowStringLiteral, symbolToOriginInfoMap } = completionData;
371375
// Find the symbol with the matching entry name.
372376
// We don't need to perform character checks here because we're only comparing the
373377
// name against 'entryName' (which is known to be good), not building a new
374378
// completion entry.
375379
const symbol = find(symbols, s =>
376380
getCompletionEntryDisplayNameForSymbol(s, compilerOptions.target, /*performCharacterChecks*/ false, allowStringLiteral) === name
377381
&& getSourceFromOrigin(symbolToOriginInfoMap[getSymbolId(s)]) === source);
378-
return symbol && { symbol, location, symbolToOriginInfoMap };
382+
return symbol ? { type: "symbol", symbol, location, symbolToOriginInfoMap } : { type: "none" };
379383
}
380384

381385
export interface CompletionEntryIdentifier {
@@ -397,29 +401,44 @@ namespace ts.Completions {
397401
const { name, source } = entryId;
398402
// Compute all the completion symbols again.
399403
const symbolCompletion = getSymbolCompletionFromEntryId(typeChecker, log, compilerOptions, sourceFile, position, entryId, allSourceFiles);
400-
if (symbolCompletion) {
401-
const { symbol, location, symbolToOriginInfoMap } = symbolCompletion;
402-
const codeActions = getCompletionEntryCodeActions(symbolToOriginInfoMap, symbol, typeChecker, host, compilerOptions, sourceFile, rulesProvider);
403-
const kindModifiers = SymbolDisplay.getSymbolModifiers(symbol);
404-
const { displayParts, documentation, symbolKind, tags } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, location, location, SemanticMeaning.All);
405-
return { name, kindModifiers, kind: symbolKind, displayParts, documentation, tags, codeActions, source: source === undefined ? undefined : [textPart(source)] };
406-
}
407-
408-
// Didn't find a symbol with this name. See if we can find a keyword instead.
409-
if (source === undefined && some(getKeywordCompletions(KeywordCompletionFilters.None), c => c.name === name)) {
410-
return {
411-
name,
412-
kind: ScriptElementKind.keyword,
413-
kindModifiers: ScriptElementKindModifier.none,
414-
displayParts: [displayPart(name, SymbolDisplayPartKind.keyword)],
415-
documentation: undefined,
416-
tags: undefined,
417-
codeActions: undefined,
418-
source: undefined,
419-
};
404+
switch (symbolCompletion.type) {
405+
case "request": {
406+
const { request } = symbolCompletion;
407+
switch (request.kind) {
408+
case "JsDocTagName":
409+
return JsDoc.getJSDocTagNameCompletionDetails(name);
410+
case "JsDocTag":
411+
return JsDoc.getJSDocTagCompletionDetails(name);
412+
case "JsDocParameterName":
413+
return JsDoc.getJSDocParameterNameCompletionDetails(name);
414+
default:
415+
return Debug.assertNever(request);
416+
}
417+
}
418+
case "symbol": {
419+
const { symbol, location, symbolToOriginInfoMap } = symbolCompletion;
420+
const codeActions = getCompletionEntryCodeActions(symbolToOriginInfoMap, symbol, typeChecker, host, compilerOptions, sourceFile, rulesProvider);
421+
const kindModifiers = SymbolDisplay.getSymbolModifiers(symbol);
422+
const { displayParts, documentation, symbolKind, tags } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, location, location, SemanticMeaning.All);
423+
return { name, kindModifiers, kind: symbolKind, displayParts, documentation, tags, codeActions, source: source === undefined ? undefined : [textPart(source)] };
424+
}
425+
case "none": {
426+
// Didn't find a symbol with this name. See if we can find a keyword instead.
427+
if (some(getKeywordCompletions(KeywordCompletionFilters.None), c => c.name === name)) {
428+
return {
429+
name,
430+
kind: ScriptElementKind.keyword,
431+
kindModifiers: ScriptElementKindModifier.none,
432+
displayParts: [displayPart(name, SymbolDisplayPartKind.keyword)],
433+
documentation: undefined,
434+
tags: undefined,
435+
codeActions: undefined,
436+
source: undefined,
437+
};
438+
}
439+
return undefined;
440+
}
420441
}
421-
422-
return undefined;
423442
}
424443

425444
function getCompletionEntryCodeActions(
@@ -461,16 +480,16 @@ namespace ts.Completions {
461480
allSourceFiles: ReadonlyArray<SourceFile>,
462481
): Symbol | undefined {
463482
const completion = getSymbolCompletionFromEntryId(typeChecker, log, compilerOptions, sourceFile, position, entryId, allSourceFiles);
464-
return completion && completion.symbol;
483+
return completion.type === "symbol" ? completion.symbol : undefined;
465484
}
466485

467486
interface CompletionData {
468-
symbols: Symbol[];
487+
symbols: ReadonlyArray<Symbol>;
469488
isGlobalCompletion: boolean;
470489
isMemberCompletion: boolean;
471490
allowStringLiteral: boolean;
472491
isNewIdentifierLocation: boolean;
473-
location: Node;
492+
location: Node | undefined;
474493
isRightOfDot: boolean;
475494
request?: Request;
476495
keywordFilters: KeywordCompletionFilters;
@@ -557,7 +576,7 @@ namespace ts.Completions {
557576

558577
if (request) {
559578
return {
560-
symbols: undefined,
579+
symbols: emptyArray,
561580
isGlobalCompletion: false,
562581
isMemberCompletion: false,
563582
allowStringLiteral: false,

src/services/jsDoc.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ namespace ts.JsDoc {
108108
}));
109109
}
110110

111+
export const getJSDocTagNameCompletionDetails = getJSDocTagCompletionDetails;
112+
111113
export function getJSDocTagCompletions(): CompletionEntry[] {
112114
return jsDocTagCompletionEntries || (jsDocTagCompletionEntries = ts.map(jsDocTagNames, tagName => {
113115
return {
@@ -119,6 +121,18 @@ namespace ts.JsDoc {
119121
}));
120122
}
121123

124+
export function getJSDocTagCompletionDetails(name: string): CompletionEntryDetails {
125+
return {
126+
name,
127+
kind: ScriptElementKind.unknown, // TODO: should have its own kind?
128+
kindModifiers: "",
129+
displayParts: [textPart(name)],
130+
documentation: emptyArray,
131+
tags: emptyArray,
132+
codeActions: undefined,
133+
};
134+
}
135+
122136
export function getJSDocParameterNameCompletions(tag: JSDocParameterTag): CompletionEntry[] {
123137
if (!isIdentifier(tag.name)) {
124138
return emptyArray;
@@ -141,6 +155,18 @@ namespace ts.JsDoc {
141155
});
142156
}
143157

158+
export function getJSDocParameterNameCompletionDetails(name: string): CompletionEntryDetails {
159+
return {
160+
name,
161+
kind: ScriptElementKind.parameterElement,
162+
kindModifiers: "",
163+
displayParts: [textPart(name)],
164+
documentation: emptyArray,
165+
tags: emptyArray,
166+
codeActions: undefined,
167+
};
168+
}
169+
144170
/**
145171
* Checks if position points to a valid position to add JSDoc comments, and if so,
146172
* returns the appropriate template. Otherwise returns an empty string.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
/////**
4+
//// * @typedef {object} T
5+
//// * /**/
6+
//// */
7+
8+
goTo.marker();
9+
verify.completionListContains("@property", "@property", "", "keyword");

0 commit comments

Comments
 (0)