diff --git a/src/services/jsDoc.ts b/src/services/jsDoc.ts index a10f736f25b83..2e2bc61136a3f 100644 --- a/src/services/jsDoc.ts +++ b/src/services/jsDoc.ts @@ -92,9 +92,20 @@ namespace ts.JsDoc { // from Array - Array and Array const parts: SymbolDisplayPart[][] = []; forEachUnique(declarations, declaration => { - for (const { comment } of getCommentHavingNodes(declaration)) { - if (comment === undefined) continue; - const newparts = getDisplayPartsFromComment(comment, checker); + for (const jsdoc of getCommentHavingNodes(declaration)) { + // skip comments containing @typedefs since they're not associated with particular declarations + // Exceptions: + // - @typedefs are themselves declarations with associated comments + // - @param or @return indicate that the author thinks of it as a 'local' @typedef that's part of the function documentation + if (jsdoc.comment === undefined + || isJSDoc(jsdoc) + && declaration.kind !== SyntaxKind.JSDocTypedefTag && declaration.kind !== SyntaxKind.JSDocCallbackTag + && jsdoc.tags + && jsdoc.tags.some(t => t.kind === SyntaxKind.JSDocTypedefTag || t.kind === SyntaxKind.JSDocCallbackTag) + && !jsdoc.tags.some(t => t.kind === SyntaxKind.JSDocParameterTag || t.kind === SyntaxKind.JSDocReturnTag)) { + continue; + } + const newparts = getDisplayPartsFromComment(jsdoc.comment, checker); if (!contains(parts, newparts, isIdenticalListOfDisplayParts)) { parts.push(newparts); } @@ -122,13 +133,21 @@ namespace ts.JsDoc { export function getJsDocTagsFromDeclarations(declarations?: Declaration[], checker?: TypeChecker): JSDocTagInfo[] { // Only collect doc comments from duplicate declarations once. - const tags: JSDocTagInfo[] = []; + const infos: JSDocTagInfo[] = []; forEachUnique(declarations, declaration => { - for (const tag of getJSDocTags(declaration)) { - tags.push({ name: tag.tagName.text, text: getCommentDisplayParts(tag, checker) }); + const tags = getJSDocTags(declaration); + // skip comments containing @typedefs since they're not associated with particular declarations + // Exceptions: + // - @param or @return indicate that the author thinks of it as a 'local' @typedef that's part of the function documentation + if (tags.some(t => t.kind === SyntaxKind.JSDocTypedefTag || t.kind === SyntaxKind.JSDocCallbackTag) + && !tags.some(t => t.kind === SyntaxKind.JSDocParameterTag || t.kind === SyntaxKind.JSDocReturnTag)) { + return; + } + for (const tag of tags) { + infos.push({ name: tag.tagName.text, text: getCommentDisplayParts(tag, checker) }); } }); - return tags; + return infos; } function getDisplayPartsFromComment(comment: string | readonly JSDocComment[], checker: TypeChecker | undefined): SymbolDisplayPart[] { diff --git a/tests/baselines/reference/quickInfoTypedefTag.baseline b/tests/baselines/reference/quickInfoTypedefTag.baseline new file mode 100644 index 0000000000000..c9dce5f682dc2 --- /dev/null +++ b/tests/baselines/reference/quickInfoTypedefTag.baseline @@ -0,0 +1,210 @@ +[ + { + "marker": { + "fileName": "/tests/cases/fourslash/a.js", + "position": 114, + "name": "1" + }, + "quickInfo": { + "kind": "function", + "kindModifiers": "", + "textSpan": { + "start": 113, + "length": 1 + }, + "displayParts": [ + { + "text": "function", + "kind": "keyword" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "f", + "kind": "functionName" + }, + { + "text": "(", + "kind": "punctuation" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "void", + "kind": "keyword" + } + ], + "documentation": [] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/a.js", + "position": 316, + "name": "2" + }, + "quickInfo": { + "kind": "function", + "kindModifiers": "", + "textSpan": { + "start": 315, + "length": 1 + }, + "displayParts": [ + { + "text": "function", + "kind": "keyword" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "g", + "kind": "functionName" + }, + { + "text": "(", + "kind": "punctuation" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "void", + "kind": "keyword" + } + ], + "documentation": [] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/a.js", + "position": 472, + "name": "3" + }, + "quickInfo": { + "kind": "function", + "kindModifiers": "", + "textSpan": { + "start": 471, + "length": 1 + }, + "displayParts": [ + { + "text": "function", + "kind": "keyword" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "h", + "kind": "functionName" + }, + { + "text": "(", + "kind": "punctuation" + }, + { + "text": "keep", + "kind": "parameterName" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "Local", + "kind": "aliasName" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "void", + "kind": "keyword" + } + ], + "documentation": [ + { + "text": "The whole thing is kept", + "kind": "text" + } + ], + "tags": [ + { + "name": "param", + "text": [ + { + "text": "keep", + "kind": "text" + } + ] + }, + { + "name": "typedef", + "text": [ + { + "text": "Local", + "kind": "aliasName" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "kept too", + "kind": "text" + } + ] + }, + { + "name": "returns", + "text": [ + { + "text": "also kept", + "kind": "text" + } + ] + } + ] + } + } +] \ No newline at end of file diff --git a/tests/cases/fourslash/quickInfoTypedefTag.ts b/tests/cases/fourslash/quickInfoTypedefTag.ts new file mode 100644 index 0000000000000..e146e4dda6258 --- /dev/null +++ b/tests/cases/fourslash/quickInfoTypedefTag.ts @@ -0,0 +1,27 @@ +/// +// @allowJs: true +// @Filename: a.js +//// /** +//// * The typedef tag should not appear in the quickinfo. +//// * @typedef {{ foo: 'foo' }} Foo +//// */ +//// function f() { } +//// f/*1*/() +//// /** +//// * A removed comment +//// * @tag Usage shows that non-param tags in comments explain the typedef instead of using it +//// * @typedef {{ nope: any }} Nope not here +//// * @tag comment 2 +//// */ +//// function g() { } +//// g/*2*/() +//// /** +//// * The whole thing is kept +//// * @param {Local} keep +//// * @typedef {{ local: any }} Local kept too +//// * @returns {void} also kept +//// */ +//// function h(keep) { } +//// h/*3*/({ nope: 1 }) + +verify.baselineQuickInfo()