Use documentation comments from inherited properties when @inheritDoc is present#18804
Use documentation comments from inherited properties when @inheritDoc is present#188048 commits merged intomicrosoft:masterfrom
Conversation
|
@Andy-MS can you please review this change. |
|
For posterity: rebased on fcb48dd and re-ran |
df96aba to
6ff0aed
Compare
src/services/services.ts
Outdated
| if (this.documentationComment === undefined) { | ||
| this.documentationComment = this.declaration ? JsDoc.getJsDocCommentsFromDeclarations([this.declaration]) : []; | ||
|
|
||
| if (this.declaration && this.documentationComment.length === 0 && hasJSDocInheritDocTag(this) && typeChecker) { |
There was a problem hiding this comment.
This might whole block might be neater as if (this.declaration) { ... } else { this.documentationComment = []; }
src/services/services.ts
Outdated
| * @returns `true` if `symbol` has a JSDoc "inheritDoc" tag on it, otherwise `false`. | ||
| */ | ||
| function hasJSDocInheritDocTag(symbol: Signature | Symbol) { | ||
| return !!find(symbol.getJsDocTags(), tag => tag.name === "inheritDoc"); |
src/services/jsDoc.ts
Outdated
| forEachUnique(declarations, declaration => { | ||
| for (const tag of getJSDocTags(declaration)) { | ||
| if (tag.kind === SyntaxKind.JSDocTag) { | ||
| if (tag.kind === SyntaxKind.JSDocTag || tag.kind === SyntaxKind.JSDocInheritDocTag) { |
There was a problem hiding this comment.
Don't think you need to modify this function for what you want; use ts.getJSDocTags (from compiler/utilities.ts) instead, which returns tags of all kinds.
src/services/services.ts
Outdated
| function findInheritedJSDocComments(declaration: Declaration, propertyName: string, typeChecker: TypeChecker): SymbolDisplayPart[] { | ||
| let documentationComment: SymbolDisplayPart[] = []; | ||
|
|
||
| if (isClassDeclaration(declaration.parent) || isInterfaceDeclaration(declaration.parent)) { |
There was a problem hiding this comment.
Prefer an early return. Also, use isClassLike.
src/services/services.ts
Outdated
| let documentationComment: SymbolDisplayPart[] = []; | ||
|
|
||
| if (isClassDeclaration(declaration.parent) || isInterfaceDeclaration(declaration.parent)) { | ||
| const container: ClassDeclaration | InterfaceDeclaration = declaration.parent; |
There was a problem hiding this comment.
You can just declare this outside (as a Node) and rely on isClassDeclaration and isInterfaceDeclaration working as type predicates.
src/services/services.ts
Outdated
| const container: ClassDeclaration | InterfaceDeclaration = declaration.parent; | ||
| const baseTypeNode = getClassExtendsHeritageClauseElement(container); | ||
|
|
||
| if (baseTypeNode) { |
src/services/services.ts
Outdated
|
|
||
| if (isClassDeclaration(declaration.parent) || isInterfaceDeclaration(declaration.parent)) { | ||
| const container: ClassDeclaration | InterfaceDeclaration = declaration.parent; | ||
| const baseTypeNode = getClassExtendsHeritageClauseElement(container); |
There was a problem hiding this comment.
There's a function getBaseTypes in checker.ts that might be better suited. You could expose it internally by adding /* @internal */ getBaseTypes(): ReadonlyArray<BaseType>; to interface TypeChecker.
The advantages are that this gets you both classes and interfaces, and you don't have to call getTypeAtLocation.
| ////const p1 = b.property1/*4*/; | ||
| ////const p2 = b.property2/*5*/; | ||
|
|
||
| verify.baselineQuickInfo(); |
There was a problem hiding this comment.
This would be breifer using just verify.quickInfoAt -- baselineQuickInfo is more so we can check all the displayParts, but that's not what we're testing here.
|
What if we just inherited documentation by default without requiring users to add |
|
Thanks for the review, @Andy-MS! I'll get this fixed up today or tomorrow.
I think it'd be pretty intuitive to be honest. I'd considered doing that here but didn't want to take too big of a step 😃 Should be relatively simple to get working, so I'll switch to that approach. Thanks again! ❤️ |
6ff0aed to
f3f6769
Compare
|
Got a few updates here based on your feedback!
I unfortunately don't see a way to get Thanks for your patience while I updated this 😄 |
|
OK, I was wrong about function findInheritedJSDocComments(declaration: Declaration, propertyName: string, typeChecker: TypeChecker): SymbolDisplayPart[] {
return flatMap(getAllSuperTypeNodes(declaration), superType => {
const baseProperty = typeChecker.getPropertyOfType(typeChecker.getTypeAtLocation(superType), propertyName);
return baseProperty && baseProperty.getDocumentationComment(typeChecker);
});
}
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;
} |
src/services/services.ts
Outdated
| if (this.declarations) { | ||
| this.documentationComment = JsDoc.getJsDocCommentsFromDeclarations(this.declarations); | ||
|
|
||
| if (this.documentationComment.length === 0 || this.declarations.some(dec => hasJSDocInheritDocTag(dec))) { |
There was a problem hiding this comment.
Nit: dec => hasJSDocInheritDocTag(dec) -- just use hasJSDocInheritDocTag directly
src/services/services.ts
Outdated
| if (this.declaration) { | ||
| this.documentationComment = JsDoc.getJsDocCommentsFromDeclarations([this.declaration]); | ||
|
|
||
| if (this.documentationComment.length === 0) { |
There was a problem hiding this comment.
Here's a case where I think we should use both inherited and own doc comments:
///<reference path="fourslash.ts" />
////interface I {
//// /** Interface doc */
//// m(): void;
////}
////
////class C implements I {
//// /**
//// * Class doc
//// * @inheritDoc
//// */
//// m/**/() {}
////}
verify.quickInfoAt("", "(method) C.m(): void", "Inferface doc\nClass doc");There was a problem hiding this comment.
Great point! I agree. This would make TypeScript's handling of @inheritDoc different than vanilla JSDoc's though, which ignores documentation on anything with @inheritDoc, e.g.:
/** @class */
class Foo {
/** Foo docs. */
test() {
}
}
/**
* @class
* @extends Foo
*/
class Bar {
/**
* Bar docs.
* @inheritDoc
*/
test() {
}
}results in JSDoc for Bar#test of "Foo docs" only.
That said, I think it's helpful for TypeScript users and I'd love to have it. As long as you're okay with differing from upstream here, I'm 100% fine with it 👍
There was a problem hiding this comment.
You're right, http://usejsdoc.org/tags-inheritdoc.html says:
The @inheritdoc tag indicates that a symbol should inherit its documentation from its parent class. Any other tags that you include in the JSDoc comment will be ignored.
This tag is provided for compatibility with Closure Compiler. By default, if you do not add a JSDoc comment to a symbol, the symbol will inherit documentation from its parent.
The presence of the @inheritdoc tag implies the presence of the @OverRide tag.
This makes it a rather strange tag -- one whose sole purpose is to make all documentation on the current node ignored! Presumably people who don't want to see that comment in quick info should just make it a regular comment and not a doc comment. I think it would be a more useful tag if it meant to add docs from a parent in addition to anything on the child.
There was a problem hiding this comment.
Makes perfect sense to me. I've updated the PR to support that :)
src/compiler/parser.ts
Outdated
| case "constructor": | ||
| tag = parseClassTag(atToken, tagName); | ||
| break; | ||
| case "inheritDoc": |
There was a problem hiding this comment.
Doesn't seem to be needed as its own SyntaxKind any more?
src/services/types.ts
Outdated
| getName(): string; | ||
| getDeclarations(): Declaration[] | undefined; | ||
| getDocumentationComment(): SymbolDisplayPart[]; | ||
| getDocumentationComment(typeChecker?: TypeChecker): SymbolDisplayPart[]; |
There was a problem hiding this comment.
Based on #19455 (comment) maybe it would be better to just take a compile-time breaking change and make this typeChecker: TypeChecker | undefined. @mhegazy
351dcbf to
424606c
Compare
|
Well this is embarrassing, I forgot to update |
…is present The JSDoc `@ineheritDoc` [tag](http://usejsdoc.org/tags-inheritdoc.html) "indicates that a symbol should inherit its documentation from its parent class". In the case of a TypeScript file, this also includes implemented interfaces and parent interfaces. With this change, a class method or property (or an interface property) with the `@inheritDoc` tag in its JSDoc comment will automatically use the comments from its nearest ancestor that has no `@inheritDoc` tag. To prevent breaking backwards compatibility, `Symbol.getDocumentationComment` now accepts an optional `TypeChecker` instance to support this feature. fixes microsoft#8912
23ac1ed to
5ba044a
Compare
|
Rebased on 1a7a587! Dang, |
|
Thanks! |
|
Thank you @sjbarag, this is super awesome! |
The JSDoc
@ineheritDoctag "indicates that a symbol should inherit its documentation from its parent class". In the case of a TypeScript file, this also includes implemented interfaces and parent interfaces.With this change, a class method or property (or an interface property) with the
@inheritDoctag in its JSDoc comment will automatically use the comments from its nearest ancestor that has no@inheritDoctag. To prevent breaking backwards compatibility,Symbol.getDocumentationCommentnow accepts an optionalTypeCheckerinstance to support this feature.fixes #8912