Skip to content

Use documentation comments from inherited properties when @inheritDoc is present #18804

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
8 commits merged into from
Nov 6, 2017
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