Skip to content
1 change: 1 addition & 0 deletions src/services/jsDoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ namespace ts.JsDoc {
"fileOverview",
"function",
"ignore",
"inheritDoc",
"inner",
"lends",
"link",
Expand Down
96 changes: 92 additions & 4 deletions src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,9 +345,29 @@ namespace ts {
return this.declarations;
}

getDocumentationComment(): SymbolDisplayPart[] {
getDocumentationComment(checker: TypeChecker | undefined): SymbolDisplayPart[] {
if (this.documentationComment === undefined) {
this.documentationComment = JsDoc.getJsDocCommentsFromDeclarations(this.declarations);
if (this.declarations) {
this.documentationComment = JsDoc.getJsDocCommentsFromDeclarations(this.declarations);

if (this.documentationComment.length === 0 || this.declarations.some(hasJSDocInheritDocTag)) {
if (checker) {
for (const declaration of this.declarations) {
const inheritedDocs = findInheritedJSDocComments(declaration, this.getName(), checker);
if (inheritedDocs.length > 0) {
if (this.documentationComment.length > 0) {
inheritedDocs.push(ts.lineBreakPart());
}
this.documentationComment = concatenate(inheritedDocs, this.documentationComment);
break;
}
}
}
}
}
else {
this.documentationComment = [];
}
}

return this.documentationComment;
Expand Down Expand Up @@ -476,7 +496,23 @@ namespace ts {

getDocumentationComment(): SymbolDisplayPart[] {
if (this.documentationComment === undefined) {
this.documentationComment = this.declaration ? JsDoc.getJsDocCommentsFromDeclarations([this.declaration]) : [];
if (this.declaration) {
this.documentationComment = JsDoc.getJsDocCommentsFromDeclarations([this.declaration]);

if (this.documentationComment.length === 0 || hasJSDocInheritDocTag(this.declaration)) {
const inheritedDocs = findInheritedJSDocComments(this.declaration, this.declaration.symbol.getName(), this.checker);
if (this.documentationComment.length > 0) {
inheritedDocs.push(ts.lineBreakPart());
}
this.documentationComment = concatenate(
inheritedDocs,
this.documentationComment
);
}
}
else {
this.documentationComment = [];
}
}

return this.documentationComment;
Expand All @@ -491,6 +527,58 @@ namespace ts {
}
}

/**
* Returns whether or not the given node has a JSDoc "inheritDoc" tag on it.
* @param node the Node in question.
* @returns `true` if `node` has a JSDoc "inheritDoc" tag on it, otherwise `false`.
*/
function hasJSDocInheritDocTag(node: Node) {
return ts.getJSDocTags(node).some(tag => tag.tagName.text === "inheritDoc");
}

/**
* Attempts to find JSDoc comments for possibly-inherited properties. Checks superclasses then traverses
* implemented interfaces until a symbol is found with the same name and with documentation.
* @param declaration The possibly-inherited declaration to find comments for.
* @param propertyName The name of the possibly-inherited property.
* @param typeChecker A TypeChecker, used to find inherited properties.
* @returns A filled array of documentation comments if any were found, otherwise an empty array.
*/
function findInheritedJSDocComments(declaration: Declaration, propertyName: string, typeChecker: TypeChecker): SymbolDisplayPart[] {
let foundDocs = false;
return flatMap(getAllSuperTypeNodes(declaration), superTypeNode => {
if (foundDocs) {
return emptyArray;
}
const superType = typeChecker.getTypeAtLocation(superTypeNode);
if (!superType) {
return emptyArray;
}
const baseProperty = typeChecker.getPropertyOfType(superType, propertyName);
if (!baseProperty) {
return emptyArray;
}
const inheritedDocs = baseProperty.getDocumentationComment(typeChecker);
foundDocs = inheritedDocs.length > 0;
return inheritedDocs;
});
}

/**
* Finds and returns the `TypeNode` for all super classes and implemented interfaces given a declaration.
* @param declaration The possibly-inherited declaration.
* @returns A filled array of `TypeNode`s containing all super classes and implemented interfaces if any exist, otherwise an empty array.
*/
function getAllSuperTypeNodes(declaration: Declaration): ReadonlyArray<TypeNode> {
const container = declaration.parent;
if (!container || (!isClassDeclaration(container) && !isInterfaceDeclaration(container))) {
return emptyArray;
}
const extended = getClassExtendsHeritageClauseElement(container);
const types = extended ? [extended] : emptyArray;
return isClassLike(container) ? concatenate(types, getClassImplementsHeritageClauseElements(container)) : types;
}

class SourceFileObject extends NodeObject implements SourceFile {
public kind: SyntaxKind.SourceFile;
public _declarationBrand: any;
Expand Down Expand Up @@ -1390,7 +1478,7 @@ namespace ts {
kindModifiers: ScriptElementKindModifier.none,
textSpan: createTextSpan(node.getStart(), node.getWidth()),
displayParts: typeToDisplayParts(typeChecker, type, getContainerNode(node)),
documentation: type.symbol ? type.symbol.getDocumentationComment() : undefined,
documentation: type.symbol ? type.symbol.getDocumentationComment(typeChecker) : undefined,
tags: type.symbol ? type.symbol.getJsDocTags() : undefined
};
}
Expand Down
6 changes: 3 additions & 3 deletions src/services/signatureHelp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ namespace ts.SignatureHelp {
suffixDisplayParts,
separatorDisplayParts: [punctuationPart(SyntaxKind.CommaToken), spacePart()],
parameters: signatureHelpParameters,
documentation: candidateSignature.getDocumentationComment(),
documentation: candidateSignature.getDocumentationComment(typeChecker),
tags: candidateSignature.getJsDocTags()
};
});
Expand All @@ -420,7 +420,7 @@ namespace ts.SignatureHelp {

return {
name: parameter.name,
documentation: parameter.getDocumentationComment(),
documentation: parameter.getDocumentationComment(typeChecker),
displayParts,
isOptional: typeChecker.isOptionalParameter(<ParameterDeclaration>parameter.valueDeclaration)
};
Expand All @@ -438,4 +438,4 @@ namespace ts.SignatureHelp {
};
}
}
}
}
6 changes: 3 additions & 3 deletions src/services/symbolDisplay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ namespace ts.SymbolDisplay {
}

if (!documentation) {
documentation = symbol.getDocumentationComment();
documentation = symbol.getDocumentationComment(typeChecker);
tags = symbol.getJsDocTags();
if (documentation.length === 0 && symbolFlags & SymbolFlags.Property) {
// For some special property access expressions like `exports.foo = foo` or `module.exports.foo = foo`
Expand All @@ -455,7 +455,7 @@ namespace ts.SymbolDisplay {
continue;
}

documentation = rhsSymbol.getDocumentationComment();
documentation = rhsSymbol.getDocumentationComment(typeChecker);
tags = rhsSymbol.getJsDocTags();
if (documentation.length > 0) {
break;
Expand Down Expand Up @@ -522,7 +522,7 @@ namespace ts.SymbolDisplay {
displayParts.push(textPart(allSignatures.length === 2 ? "overload" : "overloads"));
displayParts.push(punctuationPart(SyntaxKind.CloseParenToken));
}
documentation = signature.getDocumentationComment();
documentation = signature.getDocumentationComment(typeChecker);
tags = signature.getJsDocTags();
}

Expand Down
4 changes: 2 additions & 2 deletions src/services/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ namespace ts {
getEscapedName(): __String;
getName(): string;
getDeclarations(): Declaration[] | undefined;
getDocumentationComment(): SymbolDisplayPart[];
getDocumentationComment(typeChecker: TypeChecker | undefined): SymbolDisplayPart[];
getJsDocTags(): JSDocTagInfo[];
}

Expand All @@ -55,7 +55,7 @@ namespace ts {
getTypeParameters(): TypeParameter[] | undefined;
getParameters(): Symbol[];
getReturnType(): Type;
getDocumentationComment(): SymbolDisplayPart[];
getDocumentationComment(typeChecker: TypeChecker | undefined): SymbolDisplayPart[];
getJsDocTags(): JSDocTagInfo[];
}

Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/APISample_jsdoc.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function parseCommentsIntoDefinition(this: any,
}

// the comments for a symbol
let comments = symbol.getDocumentationComment();
let comments = symbol.getDocumentationComment(undefined);

if (comments.length) {
definition.description = comments.map(comment => comment.kind === "lineBreak" ? comment.text : comment.text.trim().replace(/\r\n/g, "\n")).join("");
Expand Down Expand Up @@ -131,7 +131,7 @@ function parseCommentsIntoDefinition(symbol, definition, otherAnnotations) {
return;
}
// the comments for a symbol
var comments = symbol.getDocumentationComment();
var comments = symbol.getDocumentationComment(undefined);
if (comments.length) {
definition.description = comments.map(function (comment) { return comment.kind === "lineBreak" ? comment.text : comment.text.trim().replace(/\r\n/g, "\n"); }).join("");
}
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3817,7 +3817,7 @@ declare namespace ts {
getEscapedName(): __String;
getName(): string;
getDeclarations(): Declaration[] | undefined;
getDocumentationComment(): SymbolDisplayPart[];
getDocumentationComment(typeChecker: TypeChecker | undefined): SymbolDisplayPart[];
getJsDocTags(): JSDocTagInfo[];
}
interface Type {
Expand All @@ -3838,7 +3838,7 @@ declare namespace ts {
getTypeParameters(): TypeParameter[] | undefined;
getParameters(): Symbol[];
getReturnType(): Type;
getDocumentationComment(): SymbolDisplayPart[];
getDocumentationComment(typeChecker: TypeChecker | undefined): SymbolDisplayPart[];
getJsDocTags(): JSDocTagInfo[];
}
interface SourceFile {
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3817,7 +3817,7 @@ declare namespace ts {
getEscapedName(): __String;
getName(): string;
getDeclarations(): Declaration[] | undefined;
getDocumentationComment(): SymbolDisplayPart[];
getDocumentationComment(typeChecker: TypeChecker | undefined): SymbolDisplayPart[];
getJsDocTags(): JSDocTagInfo[];
}
interface Type {
Expand All @@ -3838,7 +3838,7 @@ declare namespace ts {
getTypeParameters(): TypeParameter[] | undefined;
getParameters(): Symbol[];
getReturnType(): Type;
getDocumentationComment(): SymbolDisplayPart[];
getDocumentationComment(typeChecker: TypeChecker | undefined): SymbolDisplayPart[];
getJsDocTags(): JSDocTagInfo[];
}
interface SourceFile {
Expand Down
2 changes: 1 addition & 1 deletion tests/cases/compiler/APISample_jsdoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function parseCommentsIntoDefinition(this: any,
}

// the comments for a symbol
let comments = symbol.getDocumentationComment();
let comments = symbol.getDocumentationComment(undefined);

if (comments.length) {
definition.description = comments.map(comment => comment.kind === "lineBreak" ? comment.text : comment.text.trim().replace(/\r\n/g, "\n")).join("");
Expand Down
10 changes: 5 additions & 5 deletions tests/cases/fourslash/commentsInheritance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,8 @@ verify.quickInfos({
});

goTo.marker('6');
verify.completionListContains("i1_p1", "(property) c1.i1_p1: number", "");
verify.completionListContains("i1_f1", "(method) c1.i1_f1(): void", "");
verify.completionListContains("i1_p1", "(property) c1.i1_p1: number", "i1_p1");
verify.completionListContains("i1_f1", "(method) c1.i1_f1(): void", "i1_f1");
verify.completionListContains("i1_l1", "(property) c1.i1_l1: () => void", "");
verify.completionListContains("i1_nc_p1", "(property) c1.i1_nc_p1: number", "");
verify.completionListContains("i1_nc_f1", "(method) c1.i1_nc_f1(): void", "");
Expand All @@ -276,7 +276,7 @@ verify.completionListContains("nc_p1", "(property) c1.nc_p1: number", "c1_nc_p1"
verify.completionListContains("nc_f1", "(method) c1.nc_f1(): void", "c1_nc_f1");
verify.completionListContains("nc_l1", "(property) c1.nc_l1: () => void", "");
goTo.marker('7');
verify.currentSignatureHelpDocCommentIs("");
verify.currentSignatureHelpDocCommentIs("i1_f1");
goTo.marker('8');
verify.currentSignatureHelpDocCommentIs("");
goTo.marker('9');
Expand All @@ -294,7 +294,7 @@ verify.currentSignatureHelpDocCommentIs("");

verify.quickInfos({
"6iq": "var c1_i: c1",
"7q": "(method) c1.i1_f1(): void",
"7q": ["(method) c1.i1_f1(): void", "i1_f1"],
"8q": "(method) c1.i1_nc_f1(): void",
"9q": ["(method) c1.f1(): void", "c1_f1"],
"10q": ["(method) c1.nc_f1(): void", "c1_nc_f1"],
Expand Down Expand Up @@ -515,7 +515,7 @@ verify.quickInfos({
"39q": ["(method) i2.f1(): void", "i2 f1"],
"40q": "(method) i2.nc_f1(): void",
"l37q": "(property) i2.i2_l1: () => void",
"l38q": "(property) i2.i2_nc_l1: () => void",
"l38q": "(property) i2.i2_nc_l1: () => void",
"l39q": "(property) i2.l1: () => void",
"l40q": "(property) i2.nc_l1: () => void",
});
Expand Down
57 changes: 57 additions & 0 deletions tests/cases/fourslash/jsDocInheritDoc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
///<reference path="fourslash.ts" />
// @Filename: inheritDoc.ts
////class Foo {
//// /**
//// * Foo constructor documentation
//// */
//// constructor(value: number) {}
//// /**
//// * Foo#method1 documentation
//// */
//// static method1() {}
//// /**
//// * Foo#method2 documentation
//// */
//// method2() {}
//// /**
//// * Foo#property1 documentation
//// */
//// property1: string;
////}
////interface Baz {
//// /** Baz#property1 documentation */
//// property1: string;
//// /**
//// * Baz#property2 documentation
//// */
//// property2: object;
////}
////class Bar extends Foo implements Baz {
//// ctorValue: number;
//// /** @inheritDoc */
//// constructor(value: number) {
//// super(value);
//// this.ctorValue = value;
//// }
//// /** @inheritDoc */
//// static method1() {}
//// method2() {}
//// /** @inheritDoc */
//// property1: string;
//// /**
//// * Bar#property2
//// * @inheritDoc
//// */
//// property2: object;
////}
////const b = new Bar/*1*/(5);
////b.method2/*2*/();
////Bar.method1/*3*/();
////const p1 = b.property1/*4*/;
////const p2 = b.property2/*5*/;

verify.quickInfoAt("1", "constructor Bar(value: number): Bar", undefined); // constructors aren't actually inherited
verify.quickInfoAt("2", "(method) Bar.method2(): void", "Foo#method2 documentation"); // use inherited docs only
verify.quickInfoAt("3", "(method) Bar.method1(): void", undefined); // statics aren't actually inherited
verify.quickInfoAt("4", "(property) Bar.property1: string", "Foo#property1 documentation"); // use inherited docs only
verify.quickInfoAt("5", "(property) Bar.property2: object", "Baz#property2 documentation\nBar#property2"); // include local and inherited docs