Add JSDoc's @inheritDoc Support for Static Class Members for TypeScript (#46719)

* Add JSDoc's @inheritDoc Support for Static Class Members for TypeScript

* use public api

* fix

* add tests

* simplify implementation

* extract comments from inherticDoc
This commit is contained in:
Zzzen 2022-05-10 06:57:27 +08:00 committed by GitHub
parent 69214c0e56
commit 604e5fca55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 721 additions and 7 deletions

View File

@ -93,11 +93,12 @@ namespace ts.JsDoc {
const parts: SymbolDisplayPart[][] = [];
forEachUnique(declarations, declaration => {
for (const jsdoc of getCommentHavingNodes(declaration)) {
const inheritDoc = isJSDoc(jsdoc) && jsdoc.tags && find(jsdoc.tags, t => t.kind === SyntaxKind.JSDocTag && (t.tagName.escapedText === "inheritDoc" || t.tagName.escapedText === "inheritdoc"));
// 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
if (jsdoc.comment === undefined && !inheritDoc
|| isJSDoc(jsdoc)
&& declaration.kind !== SyntaxKind.JSDocTypedefTag && declaration.kind !== SyntaxKind.JSDocCallbackTag
&& jsdoc.tags
@ -105,7 +106,10 @@ namespace ts.JsDoc {
&& !jsdoc.tags.some(t => t.kind === SyntaxKind.JSDocParameterTag || t.kind === SyntaxKind.JSDocReturnTag)) {
continue;
}
const newparts = getDisplayPartsFromComment(jsdoc.comment, checker);
let newparts = jsdoc.comment ? getDisplayPartsFromComment(jsdoc.comment, checker) : [];
if (inheritDoc && inheritDoc.comment) {
newparts = newparts.concat(getDisplayPartsFromComment(inheritDoc.comment, checker));
}
if (!contains(parts, newparts, isIdenticalListOfDisplayParts)) {
parts.push(newparts);
}

View File

@ -592,7 +592,7 @@ namespace ts {
* @returns `true` if `node` has a JSDoc "inheritDoc" tag on it, otherwise `false`.
*/
function hasJSDocInheritDocTag(node: Node) {
return getJSDocTags(node).some(tag => tag.tagName.text === "inheritDoc");
return getJSDocTags(node).some(tag => tag.tagName.text === "inheritDoc" || tag.tagName.text === "inheritdoc");
}
function getJsDocTagsOfDeclarations(declarations: Declaration[] | undefined, checker: TypeChecker | undefined): JSDocTagInfo[] {
@ -643,13 +643,15 @@ namespace ts {
}
function findBaseOfDeclaration<T>(checker: TypeChecker, declaration: Declaration, cb: (symbol: Symbol) => T[] | undefined): T[] | undefined {
if (hasStaticModifier(declaration)) return;
const classOrInterfaceDeclaration = declaration.parent?.kind === SyntaxKind.Constructor ? declaration.parent.parent : declaration.parent;
if (!classOrInterfaceDeclaration) return;
const isStaticMember = hasStaticModifier(declaration);
return firstDefined(getAllSuperTypeNodes(classOrInterfaceDeclaration), superTypeNode => {
const symbol = checker.getPropertyOfType(checker.getTypeAtLocation(superTypeNode), declaration.symbol.name);
const baseType = checker.getTypeAtLocation(superTypeNode);
const symbol = isStaticMember
? find(checker.getExportsOfModule(baseType.symbol), s => s.escapedName === declaration.symbol.name)
: checker.getPropertyOfType(baseType, declaration.symbol.name);
return symbol ? cb(symbol) : undefined;
});
}

View File

@ -0,0 +1,418 @@
[
{
"marker": {
"fileName": "/tests/cases/fourslash/quickInfoInheritDoc.ts",
"position": 817,
"name": "1"
},
"quickInfo": {
"kind": "method",
"kindModifiers": "public,static",
"textSpan": {
"start": 817,
"length": 17
},
"displayParts": [
{
"text": "(",
"kind": "punctuation"
},
{
"text": "method",
"kind": "text"
},
{
"text": ")",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "SubClass",
"kind": "className"
},
{
"text": ".",
"kind": "punctuation"
},
{
"text": "doSomethingUseful",
"kind": "methodName"
},
{
"text": "(",
"kind": "punctuation"
},
{
"text": "mySpecificStuff",
"kind": "parameterName"
},
{
"text": "?",
"kind": "punctuation"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "{",
"kind": "punctuation"
},
{
"text": "\n",
"kind": "lineBreak"
},
{
"text": " ",
"kind": "space"
},
{
"text": "tiger",
"kind": "propertyName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "string",
"kind": "keyword"
},
{
"text": ";",
"kind": "punctuation"
},
{
"text": "\n",
"kind": "lineBreak"
},
{
"text": " ",
"kind": "space"
},
{
"text": "lion",
"kind": "propertyName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "string",
"kind": "keyword"
},
{
"text": ";",
"kind": "punctuation"
},
{
"text": "\n",
"kind": "lineBreak"
},
{
"text": "}",
"kind": "punctuation"
},
{
"text": ")",
"kind": "punctuation"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "string",
"kind": "keyword"
}
],
"documentation": [
{
"text": "Useful description always applicable",
"kind": "text"
}
],
"tags": [
{
"name": "returns",
"text": [
{
"text": "Useful description of return value always applicable.",
"kind": "text"
}
]
},
{
"name": "inheritDoc"
},
{
"name": "param",
"text": [
{
"text": "mySpecificStuff",
"kind": "parameterName"
},
{
"text": " ",
"kind": "space"
},
{
"text": "Description of my specific parameter.",
"kind": "text"
}
]
}
]
}
},
{
"marker": {
"fileName": "/tests/cases/fourslash/quickInfoInheritDoc.ts",
"position": 1143,
"name": "2"
},
"quickInfo": {
"kind": "method",
"kindModifiers": "public,static",
"textSpan": {
"start": 1143,
"length": 5
},
"displayParts": [
{
"text": "(",
"kind": "punctuation"
},
{
"text": "method",
"kind": "text"
},
{
"text": ")",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "SubClass",
"kind": "className"
},
{
"text": ".",
"kind": "punctuation"
},
{
"text": "func1",
"kind": "methodName"
},
{
"text": "(",
"kind": "punctuation"
},
{
"text": "stuff1",
"kind": "parameterName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "any",
"kind": "keyword"
},
{
"text": ")",
"kind": "punctuation"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "void",
"kind": "keyword"
}
],
"documentation": [
{
"text": "BaseClass.func1",
"kind": "text"
}
],
"tags": [
{
"name": "param",
"text": [
{
"text": "stuff1",
"kind": "parameterName"
},
{
"text": " ",
"kind": "space"
},
{
"text": "BaseClass.func1.stuff1",
"kind": "text"
}
]
},
{
"name": "returns",
"text": [
{
"text": "BaseClass.func1.returns",
"kind": "text"
}
]
},
{
"name": "inheritDoc"
},
{
"name": "param",
"text": [
{
"text": "stuff1",
"kind": "parameterName"
},
{
"text": " ",
"kind": "space"
},
{
"text": "SubClass.func1.stuff1",
"kind": "text"
}
]
},
{
"name": "returns",
"text": [
{
"text": "SubClass.func1.returns",
"kind": "text"
}
]
}
]
}
},
{
"marker": {
"fileName": "/tests/cases/fourslash/quickInfoInheritDoc.ts",
"position": 1282,
"name": "3"
},
"quickInfo": {
"kind": "property",
"kindModifiers": "public,static",
"textSpan": {
"start": 1282,
"length": 12
},
"displayParts": [
{
"text": "(",
"kind": "punctuation"
},
{
"text": "property",
"kind": "text"
},
{
"text": ")",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "SubClass",
"kind": "className"
},
{
"text": ".",
"kind": "punctuation"
},
{
"text": "someProperty",
"kind": "propertyName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "string",
"kind": "keyword"
}
],
"documentation": [
{
"text": "Applicable description always.",
"kind": "text"
},
{
"text": "\n",
"kind": "lineBreak"
},
{
"text": "text over tag",
"kind": "text"
},
{
"text": "text after tag",
"kind": "text"
}
],
"tags": [
{
"name": "inheritDoc",
"text": [
{
"text": "text after tag",
"kind": "text"
}
]
}
]
}
}
]

View File

@ -0,0 +1,96 @@
[
{
"marker": {
"fileName": "/tests/cases/fourslash/quickInfoInheritDoc2.ts",
"position": 173,
"name": "1"
},
"quickInfo": {
"kind": "property",
"kindModifiers": "",
"textSpan": {
"start": 173,
"length": 4
},
"displayParts": [
{
"text": "(",
"kind": "punctuation"
},
{
"text": "property",
"kind": "text"
},
{
"text": ")",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "SubClass",
"kind": "className"
},
{
"text": "<",
"kind": "punctuation"
},
{
"text": "T",
"kind": "typeParameterName"
},
{
"text": ">",
"kind": "punctuation"
},
{
"text": ".",
"kind": "punctuation"
},
{
"text": "prop",
"kind": "propertyName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "T",
"kind": "typeParameterName"
}
],
"documentation": [
{
"text": "Base.prop",
"kind": "text"
},
{
"text": "\n",
"kind": "lineBreak"
},
{
"text": "SubClass.prop",
"kind": "text"
}
],
"tags": [
{
"name": "inheritdoc",
"text": [
{
"text": "SubClass.prop",
"kind": "text"
}
]
}
]
}
}
]

View File

@ -0,0 +1,84 @@
[
{
"marker": {
"fileName": "/tests/cases/fourslash/quickInfoInheritDoc3.ts",
"position": 237,
"name": "1"
},
"quickInfo": {
"kind": "property",
"kindModifiers": "",
"textSpan": {
"start": 237,
"length": 4
},
"displayParts": [
{
"text": "(",
"kind": "punctuation"
},
{
"text": "property",
"kind": "text"
},
{
"text": ")",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "SubClass",
"kind": "className"
},
{
"text": ".",
"kind": "punctuation"
},
{
"text": "prop",
"kind": "propertyName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "string",
"kind": "keyword"
}
],
"documentation": [
{
"text": "Base.prop",
"kind": "text"
},
{
"text": "\n",
"kind": "lineBreak"
},
{
"text": "SubClass.prop",
"kind": "text"
}
],
"tags": [
{
"name": "inheritdoc",
"text": [
{
"text": "SubClass.prop",
"kind": "text"
}
]
}
]
}
}
]

View File

@ -58,7 +58,7 @@
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("3", "(method) Bar.method1(): void", 'Foo#method1 documentation'); // use inherited docs too
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
verify.quickInfoAt("6", "(property) Bar.property3: string", undefined);

View File

@ -0,0 +1,65 @@
/// <reference path="fourslash.ts" />
// @noEmit: true
// @allowJs: true
// @Filename: quickInfoInheritDoc.ts
////abstract class BaseClass {
//// /**
//// * Useful description always applicable
//// *
//// * @returns {string} Useful description of return value always applicable.
//// */
//// public static doSomethingUseful(stuff?: any): string {
//// throw new Error('Must be implemented by subclass');
//// }
////
//// /**
//// * BaseClass.func1
//// * @param {any} stuff1 BaseClass.func1.stuff1
//// * @returns {void} BaseClass.func1.returns
//// */
//// public static func1(stuff1: any): void {
//// }
////
//// /**
//// * Applicable description always.
//// */
//// public static readonly someProperty: string = 'general value';
////}
////
////
////
////
////class SubClass extends BaseClass {
////
//// /**
//// * @inheritDoc
//// *
//// * @param {{ tiger: string; lion: string; }} [mySpecificStuff] Description of my specific parameter.
//// */
//// public static /*1*/doSomethingUseful(mySpecificStuff?: { tiger: string; lion: string; }): string {
//// let useful = '';
////
//// // do something useful to useful
////
//// return useful;
//// }
////
//// /**
//// * @inheritDoc
//// * @param {any} stuff1 SubClass.func1.stuff1
//// * @returns {void} SubClass.func1.returns
//// */
//// public static /*2*/func1(stuff1: any): void {
//// }
////
//// /**
//// * text over tag
//// * @inheritDoc
//// * text after tag
//// */
//// public static readonly /*3*/someProperty: string = 'specific to this class value'
////}
verify.baselineQuickInfo();

View File

@ -0,0 +1,22 @@
/// <reference path="fourslash.ts" />
// @noEmit: true
// @allowJs: true
// @Filename: quickInfoInheritDoc2.ts
////class Base<T> {
//// /**
//// * Base.prop
//// */
//// prop: T | undefined;
////}
////
////class SubClass<T> extends Base<T> {
//// /**
//// * @inheritdoc
//// * SubClass.prop
//// */
//// /*1*/prop: T | undefined;
////}
verify.baselineQuickInfo();

View File

@ -0,0 +1,23 @@
/// <reference path="fourslash.ts" />
// @noEmit: true
// @allowJs: true
// @Filename: quickInfoInheritDoc3.ts
////function getBaseClass() {
//// return class Base {
//// /**
//// * Base.prop
//// */
//// prop: string | undefined;
//// }
////}
////class SubClass extends getBaseClass() {
//// /**
//// * @inheritdoc
//// * SubClass.prop
//// */
//// /*1*/prop: string | undefined;
////}
verify.baselineQuickInfo();