-
Notifications
You must be signed in to change notification settings - Fork 13.1k
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
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
symbol.getJsDocTags().some(...)
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Early return
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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