Skip to content

Commit 17d7aa8

Browse files
authored
Merge pull request #12856 from minestarks/includejsdoctags
Expose JSDoc tags through the language service
2 parents d73e022 + 2875d5b commit 17d7aa8

File tree

48 files changed

+1899
-529
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1899
-529
lines changed

src/compiler/parser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6468,7 +6468,7 @@ namespace ts {
64686468

64696469
function parseTagComments(indent: number) {
64706470
const comments: string[] = [];
6471-
let state = JSDocState.SawAsterisk;
6471+
let state = JSDocState.BeginningOfLine;
64726472
let margin: number | undefined;
64736473
function pushComment(text: string) {
64746474
if (!margin) {

src/compiler/utilities.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1446,7 +1446,7 @@ namespace ts {
14461446
return node && firstOrUndefined(getJSDocTags(node, kind));
14471447
}
14481448

1449-
function getJSDocs(node: Node): (JSDoc | JSDocTag)[] {
1449+
export function getJSDocs(node: Node): (JSDoc | JSDocTag)[] {
14501450
let cache: (JSDoc | JSDocTag)[] = node.jsDocCache;
14511451
if (!cache) {
14521452
getJSDocsWorker(node);

src/harness/fourslash.ts

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,7 @@ namespace FourSlash {
801801
}
802802
}
803803

804-
public verifyCompletionEntryDetails(entryName: string, expectedText: string, expectedDocumentation?: string, kind?: string) {
804+
public verifyCompletionEntryDetails(entryName: string, expectedText: string, expectedDocumentation?: string, kind?: string, tags?: ts.JSDocTagInfo[]) {
805805
const details = this.getCompletionEntryDetails(entryName);
806806

807807
assert(details, "no completion entry available");
@@ -815,6 +815,14 @@ namespace FourSlash {
815815
if (kind !== undefined) {
816816
assert.equal(details.kind, kind, this.assertionMessageAtLastKnownMarker("completion entry kind"));
817817
}
818+
819+
if (tags !== undefined) {
820+
assert.equal(details.tags.length, tags.length, this.messageAtLastKnownMarker("QuickInfo tags"));
821+
ts.zipWith(tags, details.tags, (expectedTag, actualTag) => {
822+
assert.equal(expectedTag.name, actualTag.name);
823+
assert.equal(expectedTag.text, actualTag.text, this.messageAtLastKnownMarker("QuickInfo tag " + actualTag.name));
824+
});
825+
}
818826
}
819827

820828
public verifyReferencesAre(expectedReferences: Range[]) {
@@ -961,14 +969,21 @@ namespace FourSlash {
961969

962970
public verifyQuickInfoDisplayParts(kind: string, kindModifiers: string, textSpan: { start: number; length: number; },
963971
displayParts: ts.SymbolDisplayPart[],
964-
documentation: ts.SymbolDisplayPart[]) {
972+
documentation: ts.SymbolDisplayPart[],
973+
tags: ts.JSDocTagInfo[]
974+
) {
965975

966976
const actualQuickInfo = this.languageService.getQuickInfoAtPosition(this.activeFile.fileName, this.currentCaretPosition);
967977
assert.equal(actualQuickInfo.kind, kind, this.messageAtLastKnownMarker("QuickInfo kind"));
968978
assert.equal(actualQuickInfo.kindModifiers, kindModifiers, this.messageAtLastKnownMarker("QuickInfo kindModifiers"));
969979
assert.equal(JSON.stringify(actualQuickInfo.textSpan), JSON.stringify(textSpan), this.messageAtLastKnownMarker("QuickInfo textSpan"));
970980
assert.equal(TestState.getDisplayPartsJson(actualQuickInfo.displayParts), TestState.getDisplayPartsJson(displayParts), this.messageAtLastKnownMarker("QuickInfo displayParts"));
971981
assert.equal(TestState.getDisplayPartsJson(actualQuickInfo.documentation), TestState.getDisplayPartsJson(documentation), this.messageAtLastKnownMarker("QuickInfo documentation"));
982+
assert.equal(actualQuickInfo.tags.length, tags.length, this.messageAtLastKnownMarker("QuickInfo tags"));
983+
ts.zipWith(tags, actualQuickInfo.tags, (expectedTag, actualTag) => {
984+
assert.equal(expectedTag.name, actualTag.name);
985+
assert.equal(expectedTag.text, actualTag.text, this.messageAtLastKnownMarker("QuickInfo tag " + actualTag.name));
986+
});
972987
}
973988

974989
public verifyRenameLocations(findInStrings: boolean, findInComments: boolean, ranges?: Range[]) {
@@ -1062,6 +1077,16 @@ namespace FourSlash {
10621077
assert.equal(ts.displayPartsToString(actualDocComment), docComment, this.assertionMessageAtLastKnownMarker("current signature help doc comment"));
10631078
}
10641079

1080+
public verifyCurrentSignatureHelpTags(tags: ts.JSDocTagInfo[]) {
1081+
const actualTags = this.getActiveSignatureHelpItem().tags;
1082+
1083+
assert.equal(actualTags.length, tags.length, this.assertionMessageAtLastKnownMarker("signature help tags"));
1084+
ts.zipWith(tags, actualTags, (expectedTag, actualTag) => {
1085+
assert.equal(expectedTag.name, actualTag.name);
1086+
assert.equal(expectedTag.text, actualTag.text, this.assertionMessageAtLastKnownMarker("signature help tag " + actualTag.name));
1087+
});
1088+
}
1089+
10651090
public verifySignatureHelpCount(expected: number) {
10661091
const help = this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition);
10671092
const actual = help && help.items ? help.items.length : 0;
@@ -3263,6 +3288,10 @@ namespace FourSlashInterface {
32633288
this.state.verifyCurrentSignatureHelpDocComment(docComment);
32643289
}
32653290

3291+
public currentSignatureHelpTagsAre(tags: ts.JSDocTagInfo[]) {
3292+
this.state.verifyCurrentSignatureHelpTags(tags);
3293+
}
3294+
32663295
public signatureHelpCountIs(expected: number) {
32673296
this.state.verifySignatureHelpCount(expected);
32683297
}
@@ -3383,8 +3412,8 @@ namespace FourSlashInterface {
33833412
this.state.verifyDocumentHighlightsAtPositionListCount(expectedCount, fileNamesToSearch);
33843413
}
33853414

3386-
public completionEntryDetailIs(entryName: string, text: string, documentation?: string, kind?: string) {
3387-
this.state.verifyCompletionEntryDetails(entryName, text, documentation, kind);
3415+
public completionEntryDetailIs(entryName: string, text: string, documentation?: string, kind?: string, tags?: ts.JSDocTagInfo[]) {
3416+
this.state.verifyCompletionEntryDetails(entryName, text, documentation, kind, tags);
33883417
}
33893418

33903419
/**
@@ -3414,8 +3443,8 @@ namespace FourSlashInterface {
34143443
}
34153444

34163445
public verifyQuickInfoDisplayParts(kind: string, kindModifiers: string, textSpan: { start: number; length: number; },
3417-
displayParts: ts.SymbolDisplayPart[], documentation: ts.SymbolDisplayPart[]) {
3418-
this.state.verifyQuickInfoDisplayParts(kind, kindModifiers, textSpan, displayParts, documentation);
3446+
displayParts: ts.SymbolDisplayPart[], documentation: ts.SymbolDisplayPart[], tags: ts.JSDocTagInfo[]) {
3447+
this.state.verifyQuickInfoDisplayParts(kind, kindModifiers, textSpan, displayParts, documentation, tags);
34193448
}
34203449

34213450
public getSyntacticDiagnostics(expected: string) {

src/server/client.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,8 @@ namespace ts.server {
176176
kindModifiers: response.body.kindModifiers,
177177
textSpan: ts.createTextSpanFromBounds(start, end),
178178
displayParts: [{ kind: "text", text: response.body.displayString }],
179-
documentation: [{ kind: "text", text: response.body.documentation }]
179+
documentation: [{ kind: "text", text: response.body.documentation }],
180+
tags: response.body.tags
180181
};
181182
}
182183

src/server/protocol.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,6 +1295,11 @@ namespace ts.server.protocol {
12951295
* Documentation associated with symbol.
12961296
*/
12971297
documentation: string;
1298+
1299+
/**
1300+
* JSDoc tags associated with symbol.
1301+
*/
1302+
tags: JSDocTagInfo[]
12981303
}
12991304

13001305
/**
@@ -1525,6 +1530,11 @@ namespace ts.server.protocol {
15251530
* Documentation strings for the symbol.
15261531
*/
15271532
documentation: SymbolDisplayPart[];
1533+
1534+
/**
1535+
* JSDoc tags for the symbol.
1536+
*/
1537+
tags: JSDocTagInfo[];
15281538
}
15291539

15301540
export interface CompletionsResponse extends Response {
@@ -1595,6 +1605,11 @@ namespace ts.server.protocol {
15951605
* The signature's documentation
15961606
*/
15971607
documentation: SymbolDisplayPart[];
1608+
1609+
/**
1610+
* The signature's JSDoc tags
1611+
*/
1612+
tags: JSDocTagInfo[];
15981613
}
15991614

16001615
/**

src/server/session.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -853,13 +853,15 @@ namespace ts.server {
853853
if (simplifiedResult) {
854854
const displayString = ts.displayPartsToString(quickInfo.displayParts);
855855
const docString = ts.displayPartsToString(quickInfo.documentation);
856+
856857
return {
857858
kind: quickInfo.kind,
858859
kindModifiers: quickInfo.kindModifiers,
859860
start: scriptInfo.positionToLineOffset(quickInfo.textSpan.start),
860861
end: scriptInfo.positionToLineOffset(ts.textSpanEnd(quickInfo.textSpan)),
861862
displayString: displayString,
862863
documentation: docString,
864+
tags: quickInfo.tags || []
863865
};
864866
}
865867
else {

src/services/completions.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -759,13 +759,14 @@ namespace ts.Completions {
759759
const symbol = forEach(symbols, s => getCompletionEntryDisplayNameForSymbol(typeChecker, s, compilerOptions.target, /*performCharacterChecks*/ false, location) === entryName ? s : undefined);
760760

761761
if (symbol) {
762-
const { displayParts, documentation, symbolKind } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, location, location, SemanticMeaning.All);
762+
const { displayParts, documentation, symbolKind, tags } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, location, location, SemanticMeaning.All);
763763
return {
764764
name: entryName,
765765
kindModifiers: SymbolDisplay.getSymbolModifiers(symbol),
766766
kind: symbolKind,
767767
displayParts,
768-
documentation
768+
documentation,
769+
tags
769770
};
770771
}
771772
}
@@ -778,7 +779,8 @@ namespace ts.Completions {
778779
kind: ScriptElementKind.keyword,
779780
kindModifiers: ScriptElementKindModifier.none,
780781
displayParts: [displayPart(entryName, SymbolDisplayPartKind.keyword)],
781-
documentation: undefined
782+
documentation: undefined,
783+
tags: undefined
782784
};
783785
}
784786

src/services/jsDoc.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,28 @@ namespace ts.JsDoc {
6868
return documentationComment;
6969
}
7070

71+
export function getJsDocTagsFromDeclarations(declarations: Declaration[]) {
72+
// Only collect doc comments from duplicate declarations once.
73+
const tags: JSDocTagInfo[] = [];
74+
forEachUnique(declarations, declaration => {
75+
const jsDocs = getJSDocs(declaration);
76+
if (!jsDocs) {
77+
return;
78+
}
79+
for (const doc of jsDocs) {
80+
const tagsForDoc = (doc as JSDoc).tags;
81+
if (tagsForDoc) {
82+
tags.push(...tagsForDoc.filter(tag => tag.kind === SyntaxKind.JSDocTag).map(jsDocTag => {
83+
return {
84+
name: jsDocTag.tagName.text,
85+
text: jsDocTag.comment
86+
} }));
87+
}
88+
}
89+
});
90+
return tags;
91+
}
92+
7193
/**
7294
* Iterates through 'array' by index and performs the callback on each element of array until the callback
7395
* returns a truthy value, then returns that value.

src/services/services.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,10 @@ namespace ts {
292292
// symbol has no doc comment, then the empty string will be returned.
293293
documentationComment: SymbolDisplayPart[];
294294

295+
// Undefined is used to indicate the value has not been computed. If, after computing, the
296+
// symbol has no JSDoc tags, then the empty array will be returned.
297+
tags: JSDocTagInfo[];
298+
295299
constructor(flags: SymbolFlags, name: string) {
296300
this.flags = flags;
297301
this.name = name;
@@ -316,6 +320,14 @@ namespace ts {
316320

317321
return this.documentationComment;
318322
}
323+
324+
getJsDocTags(): JSDocTagInfo[] {
325+
if (this.tags === undefined) {
326+
this.tags = JsDoc.getJsDocTagsFromDeclarations(this.declarations);
327+
}
328+
329+
return this.tags;
330+
}
319331
}
320332

321333
class TokenObject<TKind extends SyntaxKind> extends TokenOrIdentifierObject implements Token<TKind> {
@@ -404,6 +416,10 @@ namespace ts {
404416
// symbol has no doc comment, then the empty string will be returned.
405417
documentationComment: SymbolDisplayPart[];
406418

419+
// Undefined is used to indicate the value has not been computed. If, after computing, the
420+
// symbol has no doc comment, then the empty array will be returned.
421+
jsDocTags: JSDocTagInfo[];
422+
407423
constructor(checker: TypeChecker) {
408424
this.checker = checker;
409425
}
@@ -427,6 +443,14 @@ namespace ts {
427443

428444
return this.documentationComment;
429445
}
446+
447+
getJsDocTags(): JSDocTagInfo[] {
448+
if (this.jsDocTags === undefined) {
449+
this.jsDocTags = this.declaration ? JsDoc.getJsDocTagsFromDeclarations([this.declaration]) : [];
450+
}
451+
452+
return this.jsDocTags;
453+
}
430454
}
431455

432456
class SourceFileObject extends NodeObject implements SourceFile {
@@ -1315,7 +1339,8 @@ namespace ts {
13151339
kindModifiers: ScriptElementKindModifier.none,
13161340
textSpan: createTextSpan(node.getStart(), node.getWidth()),
13171341
displayParts: typeToDisplayParts(typeChecker, type, getContainerNode(node)),
1318-
documentation: type.symbol ? type.symbol.getDocumentationComment() : undefined
1342+
documentation: type.symbol ? type.symbol.getDocumentationComment() : undefined,
1343+
tags: type.symbol ? type.symbol.getJsDocTags() : undefined
13191344
};
13201345
}
13211346
}
@@ -1329,7 +1354,8 @@ namespace ts {
13291354
kindModifiers: SymbolDisplay.getSymbolModifiers(symbol),
13301355
textSpan: createTextSpan(node.getStart(), node.getWidth()),
13311356
displayParts: displayPartsDocumentationsAndKind.displayParts,
1332-
documentation: displayPartsDocumentationsAndKind.documentation
1357+
documentation: displayPartsDocumentationsAndKind.documentation,
1358+
tags: displayPartsDocumentationsAndKind.tags
13331359
};
13341360
}
13351361

src/services/signatureHelp.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,8 @@ namespace ts.SignatureHelp {
590590
suffixDisplayParts,
591591
separatorDisplayParts: [punctuationPart(SyntaxKind.CommaToken), spacePart()],
592592
parameters: signatureHelpParameters,
593-
documentation: candidateSignature.getDocumentationComment()
593+
documentation: candidateSignature.getDocumentationComment(),
594+
tags: candidateSignature.getJsDocTags()
594595
};
595596
});
596597

src/services/symbolDisplay.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ namespace ts.SymbolDisplay {
8989

9090
const displayParts: SymbolDisplayPart[] = [];
9191
let documentation: SymbolDisplayPart[];
92+
let tags: JSDocTagInfo[];
9293
const symbolFlags = symbol.flags;
9394
let symbolKind = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, symbolFlags, location);
9495
let hasAddedSymbolInfo: boolean;
@@ -407,6 +408,7 @@ namespace ts.SymbolDisplay {
407408

408409
if (!documentation) {
409410
documentation = symbol.getDocumentationComment();
411+
tags = symbol.getJsDocTags();
410412
if (documentation.length === 0 && symbol.flags & SymbolFlags.Property) {
411413
// For some special property access expressions like `experts.foo = foo` or `module.exports.foo = foo`
412414
// there documentation comments might be attached to the right hand side symbol of their declarations.
@@ -423,6 +425,7 @@ namespace ts.SymbolDisplay {
423425
}
424426

425427
documentation = rhsSymbol.getDocumentationComment();
428+
tags = rhsSymbol.getJsDocTags();
426429
if (documentation.length > 0) {
427430
break;
428431
}
@@ -431,7 +434,7 @@ namespace ts.SymbolDisplay {
431434
}
432435
}
433436

434-
return { displayParts, documentation, symbolKind };
437+
return { displayParts, documentation, symbolKind, tags };
435438

436439
function addNewLineIfDisplayPartsExist() {
437440
if (displayParts.length) {
@@ -489,6 +492,7 @@ namespace ts.SymbolDisplay {
489492
displayParts.push(punctuationPart(SyntaxKind.CloseParenToken));
490493
}
491494
documentation = signature.getDocumentationComment();
495+
tags = signature.getJsDocTags();
492496
}
493497

494498
function writeTypeParametersOfSymbol(symbol: Symbol, enclosingDeclaration: Node) {

0 commit comments

Comments
 (0)