Skip to content

Commit 79dcd3d

Browse files
jeanp413sandersn
authored andcommitted
Correctly resolve tags for function overloads (#30253)
* Correctly resolve tags for function overloads. Fixes #30181 * Better fix for #30181. Added more unit tests * Fix commentsOverloads tests * Fallback to first signature when doc and tags are empty
1 parent 5fc917b commit 79dcd3d

7 files changed

+370
-41
lines changed

src/services/symbolDisplay.ts

+39-26
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,8 @@ namespace ts.SymbolDisplay {
131131
}
132132

133133
const displayParts: SymbolDisplayPart[] = [];
134-
let documentation: SymbolDisplayPart[] | undefined;
135-
let tags: JSDocTagInfo[] | undefined;
134+
let documentation: SymbolDisplayPart[] = [];
135+
let tags: JSDocTagInfo[] = [];
136136
const symbolFlags = getCombinedLocalAndExportSymbolFlags(symbol);
137137
let symbolKind = semanticMeaning & SemanticMeaning.Value ? getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location) : ScriptElementKind.unknown;
138138
let hasAddedSymbolInfo = false;
@@ -141,6 +141,7 @@ namespace ts.SymbolDisplay {
141141
let printer: Printer;
142142
let documentationFromAlias: SymbolDisplayPart[] | undefined;
143143
let tagsFromAlias: JSDocTagInfo[] | undefined;
144+
let hasMultipleSignatures = false;
144145

145146
if (location.kind === SyntaxKind.ThisKeyword && !isThisExpression) {
146147
return { displayParts: [keywordPart(SyntaxKind.ThisKeyword)], documentation: [], symbolKind: ScriptElementKind.primitiveType, tags: undefined };
@@ -236,6 +237,7 @@ namespace ts.SymbolDisplay {
236237
addSignatureDisplayParts(signature, allSignatures);
237238
}
238239
hasAddedSymbolInfo = true;
240+
hasMultipleSignatures = allSignatures.length > 1;
239241
}
240242
}
241243
else if ((isNameOfFunctionDeclaration(location) && !(symbolFlags & SymbolFlags.Accessor)) || // name of function declaration
@@ -268,6 +270,7 @@ namespace ts.SymbolDisplay {
268270

269271
addSignatureDisplayParts(signature, allSignatures);
270272
hasAddedSymbolInfo = true;
273+
hasMultipleSignatures = allSignatures.length > 1;
271274
}
272275
}
273276
}
@@ -495,6 +498,7 @@ namespace ts.SymbolDisplay {
495498
const allSignatures = type.getNonNullableType().getCallSignatures();
496499
if (allSignatures.length) {
497500
addSignatureDisplayParts(allSignatures[0], allSignatures);
501+
hasMultipleSignatures = allSignatures.length > 1;
498502
}
499503
}
500504
}
@@ -504,42 +508,47 @@ namespace ts.SymbolDisplay {
504508
}
505509
}
506510

507-
if (!documentation) {
511+
if (documentation.length === 0 && !hasMultipleSignatures) {
508512
documentation = symbol.getDocumentationComment(typeChecker);
509-
tags = symbol.getJsDocTags();
510-
if (documentation.length === 0 && symbolFlags & SymbolFlags.Property) {
511-
// For some special property access expressions like `exports.foo = foo` or `module.exports.foo = foo`
512-
// there documentation comments might be attached to the right hand side symbol of their declarations.
513-
// The pattern of such special property access is that the parent symbol is the symbol of the file.
514-
if (symbol.parent && forEach(symbol.parent.declarations, declaration => declaration.kind === SyntaxKind.SourceFile)) {
515-
for (const declaration of symbol.declarations) {
516-
if (!declaration.parent || declaration.parent.kind !== SyntaxKind.BinaryExpression) {
517-
continue;
518-
}
513+
}
519514

520-
const rhsSymbol = typeChecker.getSymbolAtLocation((<BinaryExpression>declaration.parent).right);
521-
if (!rhsSymbol) {
522-
continue;
523-
}
515+
if (documentation.length === 0 && symbolFlags & SymbolFlags.Property) {
516+
// For some special property access expressions like `exports.foo = foo` or `module.exports.foo = foo`
517+
// there documentation comments might be attached to the right hand side symbol of their declarations.
518+
// The pattern of such special property access is that the parent symbol is the symbol of the file.
519+
if (symbol.parent && forEach(symbol.parent.declarations, declaration => declaration.kind === SyntaxKind.SourceFile)) {
520+
for (const declaration of symbol.declarations) {
521+
if (!declaration.parent || declaration.parent.kind !== SyntaxKind.BinaryExpression) {
522+
continue;
523+
}
524524

525-
documentation = rhsSymbol.getDocumentationComment(typeChecker);
526-
tags = rhsSymbol.getJsDocTags();
527-
if (documentation.length > 0) {
528-
break;
529-
}
525+
const rhsSymbol = typeChecker.getSymbolAtLocation((<BinaryExpression>declaration.parent).right);
526+
if (!rhsSymbol) {
527+
continue;
528+
}
529+
530+
documentation = rhsSymbol.getDocumentationComment(typeChecker);
531+
tags = rhsSymbol.getJsDocTags();
532+
if (documentation.length > 0) {
533+
break;
530534
}
531535
}
532536
}
533537
}
534538

539+
if (tags.length === 0 && !hasMultipleSignatures) {
540+
tags = symbol.getJsDocTags();
541+
}
542+
535543
if (documentation.length === 0 && documentationFromAlias) {
536544
documentation = documentationFromAlias;
537545
}
538-
if (tags!.length === 0 && tagsFromAlias) {
546+
547+
if (tags.length === 0 && tagsFromAlias) {
539548
tags = tagsFromAlias;
540549
}
541550

542-
return { displayParts, documentation, symbolKind, tags: tags!.length === 0 ? undefined : tags };
551+
return { displayParts, documentation, symbolKind, tags: tags.length === 0 ? undefined : tags };
543552

544553
function getPrinter() {
545554
if (!printer) {
@@ -620,9 +629,13 @@ namespace ts.SymbolDisplay {
620629
displayParts.push(textPart(allSignatures.length === 2 ? "overload" : "overloads"));
621630
displayParts.push(punctuationPart(SyntaxKind.CloseParenToken));
622631
}
623-
const docComment = signature.getDocumentationComment(typeChecker);
624-
documentation = docComment.length === 0 ? undefined : docComment;
632+
documentation = signature.getDocumentationComment(typeChecker);
625633
tags = signature.getJsDocTags();
634+
635+
if (allSignatures.length > 1 && documentation.length === 0 && tags.length === 0) {
636+
documentation = allSignatures[0].getDocumentationComment(typeChecker);
637+
tags = allSignatures[0].getJsDocTags();
638+
}
626639
}
627640

628641
function writeTypeParametersOfSymbol(symbol: Symbol, enclosingDeclaration: Node | undefined) {

tests/cases/fourslash/commentsOverloads.ts

+15-15
Original file line numberDiff line numberDiff line change
@@ -237,11 +237,11 @@ verify.signatureHelp({ marker: "4", overloadsCount: 2 });
237237
verify.signatureHelp({ marker: "o4", overloadsCount: 2, docComment: "this is signature 1", parameterDocComment: "param a" });
238238

239239
verify.quickInfos({
240-
5: ["function f2(a: number): number (+1 overload)", "this is signature 2\nthis is f2 var comment"],
240+
5: "function f2(a: number): number (+1 overload)",
241241
6: ["function f2(b: string): number (+1 overload)", "this is signature 2"],
242-
7: ["function f2(a: number): number (+1 overload)", "this is signature 2\nthis is f2 var comment"],
242+
7: "function f2(a: number): number (+1 overload)",
243243
"8q": ["function f2(b: string): number (+1 overload)", "this is signature 2"],
244-
o8q: ["function f2(a: number): number (+1 overload)", "this is signature 2\nthis is f2 var comment"],
244+
o8q: "function f2(a: number): number (+1 overload)",
245245
});
246246

247247
verify.signatureHelp(
@@ -277,15 +277,15 @@ verify.completions(
277277
marker: "17",
278278
includes: [
279279
{ name: "f1", text: "function f1(a: number): number (+1 overload)", documentation: "this is signature 1" },
280-
{ name: "f2", text: "function f2(a: number): number (+1 overload)", documentation: "this is signature 2\nthis is f2 var comment" },
280+
{ name: "f2", text: "function f2(a: number): number (+1 overload)" },
281281
{ name: "f3", text: "function f3(a: number): number (+1 overload)" },
282282
{ name: "f4", text: "function f4(a: number): number (+1 overload)", documentation: "this is signature 4 - with number parameter" },
283283
],
284284
},
285285
{
286286
marker: "18",
287287
includes: [
288-
{ name: "i1_i", text: "var i1_i: i1\nnew (b: number) => any (+1 overload)" },
288+
{ name: "i1_i", text: "var i1_i: i1\nnew (b: number) => any (+1 overload)", documentation: "new 1" },
289289
{ name: "i2_i", text: "var i2_i: i2\nnew (a: string) => any (+1 overload)" },
290290
{ name: "i3_i", text: "var i3_i: i3\nnew (a: string) => any (+1 overload)", documentation: "new 1" },
291291
{ name: "i4_i", text: "var i4_i: i4\nnew (a: string) => any (+1 overload)" },
@@ -295,7 +295,7 @@ verify.completions(
295295
);
296296

297297
verify.signatureHelp({ marker: "19", overloadsCount: 2 });
298-
verify.quickInfoAt("19q", "var i1_i: i1\nnew (b: number) => any (+1 overload)");
298+
verify.quickInfoAt("19q", "var i1_i: i1\nnew (b: number) => any (+1 overload)", "new 1");
299299

300300
verify.signatureHelp({ marker: "20", overloadsCount: 2, docComment: "new 1" });
301301
verify.quickInfoAt("20q", "var i1_i: i1\nnew (a: string) => any (+1 overload)", "new 1");
@@ -311,7 +311,7 @@ verify.completions({
311311
marker: "23",
312312
includes: [
313313
{ name: "foo" , text: "(method) i1.foo(a: number): number (+1 overload)", documentation: "foo 1" },
314-
{ name: "foo2", text: "(method) i1.foo2(a: number): number (+1 overload)", documentation: "foo2 2" },
314+
{ name: "foo2", text: "(method) i1.foo2(a: number): number (+1 overload)" },
315315
{ name: "foo3", text: "(method) i1.foo3(a: number): number (+1 overload)" },
316316
{ name: "foo4", text: "(method) i1.foo4(a: number): number (+1 overload)", documentation: "foo4 1" },
317317
],
@@ -324,7 +324,7 @@ verify.signatureHelp({ marker: "25", overloadsCount: 2, docComment: "foo 2" });
324324
verify.quickInfoAt("25q", "(method) i1.foo(b: string): number (+1 overload)", "foo 2");
325325

326326
verify.signatureHelp({ marker: "26", overloadsCount: 2 });
327-
verify.quickInfoAt("26q", "(method) i1.foo2(a: number): number (+1 overload)", "foo2 2");
327+
verify.quickInfoAt("26q", "(method) i1.foo2(a: number): number (+1 overload)");
328328

329329
verify.signatureHelp({ marker: "27", overloadsCount: 2, docComment: "foo2 2" });
330330
verify.quickInfoAt("27q", "(method) i1.foo2(b: string): number (+1 overload)", "foo2 2");
@@ -363,7 +363,7 @@ verify.signatureHelp({ marker: "38", overloadsCount: 2, docComment: "this is sig
363363
verify.quickInfoAt("38q", "var i3_i: i3\n(a: number) => number (+1 overload)", "this is signature 1");
364364

365365
verify.signatureHelp({ marker: "39", overloadsCount: 2 });
366-
verify.quickInfoAt("39q", "var i3_i: i3\n(b: string) => number (+1 overload)");
366+
verify.quickInfoAt("39q", "var i3_i: i3\n(b: string) => number (+1 overload)", "this is signature 1");
367367

368368
verify.signatureHelp({ marker: "40", overloadsCount: 2 });
369369
verify.quickInfoAt("40q", "var i4_i: i4\nnew (b: number) => any (+1 overload)");
@@ -382,7 +382,7 @@ verify.completions({
382382
exact: [
383383
{ name: "prop1", text: "(method) c.prop1(a: number): number (+1 overload)" },
384384
{ name: "prop2", text: "(method) c.prop2(a: number): number (+1 overload)", documentation: "prop2 1" },
385-
{ name: "prop3", text: "(method) c.prop3(a: number): number (+1 overload)", documentation: "prop3 2" },
385+
{ name: "prop3", text: "(method) c.prop3(a: number): number (+1 overload)" },
386386
{ name: "prop4", text: "(method) c.prop4(a: number): number (+1 overload)", documentation: "prop4 1" },
387387
{ name: "prop5", text: "(method) c.prop5(a: number): number (+1 overload)", documentation: "prop5 1" },
388388
],
@@ -401,7 +401,7 @@ verify.signatureHelp({ marker: "48", overloadsCount: 2 });
401401
verify.quickInfoAt("48q", "(method) c.prop2(b: string): number (+1 overload)", "prop2 1");
402402

403403
verify.signatureHelp({ marker: "49", overloadsCount: 2 });
404-
verify.quickInfoAt("49q", "(method) c.prop3(a: number): number (+1 overload)", "prop3 2");
404+
verify.quickInfoAt("49q", "(method) c.prop3(a: number): number (+1 overload)");
405405

406406
verify.signatureHelp({ marker: "50", overloadsCount: 2, docComment: "prop3 2" });
407407
verify.quickInfoAt("50q", "(method) c.prop3(b: string): number (+1 overload)", "prop3 2");
@@ -428,7 +428,7 @@ verify.signatureHelp({ marker: "57", overloadsCount: 2, docComment: "c2 1" });
428428
verify.quickInfoAt("57q", "constructor c2(a: number): c2 (+1 overload)", "c2 1");
429429

430430
verify.signatureHelp({ marker: "58", overloadsCount: 2 });
431-
verify.quickInfoAt("58q", "constructor c2(b: string): c2 (+1 overload)");
431+
verify.quickInfoAt("58q", "constructor c2(b: string): c2 (+1 overload)", "c2 1");
432432

433433
verify.signatureHelp({ marker: "59", overloadsCount: 2 });
434434
verify.quickInfoAt("59q", "constructor c3(a: number): c3 (+1 overload)");
@@ -490,7 +490,7 @@ verify.quickInfos({
490490
79: "constructor c1(b: string): c1 (+1 overload)",
491491
80: "constructor c1(a: number): c1 (+1 overload)",
492492
81: ["constructor c2(a: number): c2 (+1 overload)", "c2 1"],
493-
82: "constructor c2(b: string): c2 (+1 overload)",
493+
82: ["constructor c2(b: string): c2 (+1 overload)", "c2 1"],
494494
83: ["constructor c2(a: number): c2 (+1 overload)", "c2 1"],
495495
84: "constructor c3(a: number): c3 (+1 overload)",
496496
85: ["constructor c3(b: string): c3 (+1 overload)", "c3 2"],
@@ -507,9 +507,9 @@ verify.quickInfos({
507507
96: ["(method) c.prop2(a: number): number (+1 overload)", "prop2 1"],
508508
97: ["(method) c.prop2(b: string): number (+1 overload)", "prop2 1"],
509509
98: ["(method) c.prop2(a: number): number (+1 overload)", "prop2 1"],
510-
99: ["(method) c.prop3(a: number): number (+1 overload)", "prop3 2"],
510+
99: "(method) c.prop3(a: number): number (+1 overload)",
511511
100: ["(method) c.prop3(b: string): number (+1 overload)", "prop3 2"],
512-
101: ["(method) c.prop3(a: number): number (+1 overload)", "prop3 2"],
512+
101: "(method) c.prop3(a: number): number (+1 overload)",
513513
102: ["(method) c.prop4(a: number): number (+1 overload)", "prop4 1"],
514514
103: ["(method) c.prop4(b: string): number (+1 overload)", "prop4 2"],
515515
104: ["(method) c.prop4(a: number): number (+1 overload)", "prop4 1"],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
// @Filename: quickInfoJsDocTagsFunctionOverload01.ts
4+
/////**
5+
//// * Doc foo
6+
//// */
7+
////declare function /*1*/foo(): void;
8+
////
9+
/////**
10+
//// * Doc foo overloaded
11+
//// * @tag Tag text
12+
//// */
13+
////declare function /*2*/foo(x: number): void
14+
15+
goTo.marker("1");
16+
verify.verifyQuickInfoDisplayParts(
17+
"function",
18+
"declare",
19+
{ start: 36, length: 3 },
20+
[
21+
{text:"function",kind:"keyword"},
22+
{text:" ",kind:"space"},
23+
{text:"foo",kind:"functionName"},
24+
{text:"(",kind:"punctuation"},
25+
{text:")",kind:"punctuation"},
26+
{text:":",kind:"punctuation"},
27+
{text:" ",kind:"space"},
28+
{text:"void",kind:"keyword"},
29+
{text:" ",kind:"space"},
30+
{text:"(",kind:"punctuation"},
31+
{text:"+",kind:"operator"},
32+
{text:"1",kind:"numericLiteral"},
33+
{text:" ",kind:"space"},
34+
{text:"overload",kind:"text"},
35+
{text:")",kind:"punctuation"}
36+
],
37+
[{ text: "Doc foo", kind: "text" }]);
38+
39+
goTo.marker("2");
40+
verify.verifyQuickInfoDisplayParts(
41+
"function",
42+
"declare",
43+
{ start: 114, length: 3 },
44+
[
45+
{text:"function",kind:"keyword"},
46+
{text:" ",kind:"space"},
47+
{text:"foo",kind:"functionName"},
48+
{text:"(",kind:"punctuation"},
49+
{text:"x",kind:"parameterName"},
50+
{text:":",kind:"punctuation"},
51+
{text:" ",kind:"space"},
52+
{text:"number",kind:"keyword"},
53+
{text:")",kind:"punctuation"},
54+
{text:":",kind:"punctuation"},
55+
{text:" ",kind:"space"},
56+
{text:"void",kind:"keyword"},
57+
{text:" ",kind:"space"},
58+
{text:"(",kind:"punctuation"},
59+
{text:"+",kind:"operator"},
60+
{text:"1",kind:"numericLiteral"},
61+
{text:" ",kind:"space"},
62+
{text:"overload",kind:"text"},
63+
{text:")",kind:"punctuation"}
64+
],
65+
[{ text: "Doc foo overloaded", kind: "text" }],
66+
[{ name: "tag", text: "Tag text" }]);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
// @Filename: quickInfoJsDocTagsFunctionOverload02.ts
4+
/////**
5+
//// * Doc foo
6+
//// */
7+
////declare function /*1*/foo(): void;
8+
////
9+
/////**
10+
//// * Doc foo overloaded
11+
//// */
12+
////declare function /*2*/foo(x: number): void
13+
14+
goTo.marker("1");
15+
verify.verifyQuickInfoDisplayParts(
16+
"function",
17+
"declare",
18+
{ start: 36, length: 3 },
19+
[
20+
{text:"function",kind:"keyword"},
21+
{text:" ",kind:"space"},
22+
{text:"foo",kind:"functionName"},
23+
{text:"(",kind:"punctuation"},
24+
{text:")",kind:"punctuation"},
25+
{text:":",kind:"punctuation"},
26+
{text:" ",kind:"space"},
27+
{text:"void",kind:"keyword"},
28+
{text:" ",kind:"space"},
29+
{text:"(",kind:"punctuation"},
30+
{text:"+",kind:"operator"},
31+
{text:"1",kind:"numericLiteral"},
32+
{text:" ",kind:"space"},
33+
{text:"overload",kind:"text"},
34+
{text:")",kind:"punctuation"}
35+
],
36+
[{ text: "Doc foo", kind: "text" }]);
37+
38+
goTo.marker("2");
39+
verify.verifyQuickInfoDisplayParts(
40+
"function",
41+
"declare",
42+
{ start: 97, length: 3 },
43+
[
44+
{text:"function",kind:"keyword"},
45+
{text:" ",kind:"space"},
46+
{text:"foo",kind:"functionName"},
47+
{text:"(",kind:"punctuation"},
48+
{text:"x",kind:"parameterName"},
49+
{text:":",kind:"punctuation"},
50+
{text:" ",kind:"space"},
51+
{text:"number",kind:"keyword"},
52+
{text:")",kind:"punctuation"},
53+
{text:":",kind:"punctuation"},
54+
{text:" ",kind:"space"},
55+
{text:"void",kind:"keyword"},
56+
{text:" ",kind:"space"},
57+
{text:"(",kind:"punctuation"},
58+
{text:"+",kind:"operator"},
59+
{text:"1",kind:"numericLiteral"},
60+
{text:" ",kind:"space"},
61+
{text:"overload",kind:"text"},
62+
{text:")",kind:"punctuation"}
63+
],
64+
[{ text: "Doc foo overloaded", kind: "text" }]);

0 commit comments

Comments
 (0)