From 582aac49a7ee06a883a70246caedd0bcc92e24bc Mon Sep 17 00:00:00 2001 From: Matthieu Riegler Date: Wed, 21 Aug 2024 01:05:01 +0200 Subject: [PATCH] refactor(compiler-cli): Add support for inheritance in API extraction (#57588) This commit adds the `extends` and `implements` properties to the `ClassEntry` & `InterfaceEntry` PR Close #57588 --- .../src/ngtsc/docs/src/class_extractor.ts | 32 +++++++++++++++++++ .../src/ngtsc/docs/src/entities.ts | 2 ++ .../class_doc_extraction_spec.ts | 21 ++++++++++++ 3 files changed, 55 insertions(+) diff --git a/packages/compiler-cli/src/ngtsc/docs/src/class_extractor.ts b/packages/compiler-cli/src/ngtsc/docs/src/class_extractor.ts index 9e461e4f7fa..d39b0f765aa 100644 --- a/packages/compiler-cli/src/ngtsc/docs/src/class_extractor.ts +++ b/packages/compiler-cli/src/ngtsc/docs/src/class_extractor.ts @@ -85,6 +85,8 @@ class ClassExtractor { description: extractJsDocDescription(this.declaration), jsdocTags: extractJsDocTags(this.declaration), rawComment: extractRawJsDoc(this.declaration), + extends: this.extractInheritance(this.declaration), + implements: this.extractInterfaceConformance(this.declaration), }; } @@ -175,6 +177,36 @@ class ClassExtractor { }; } + protected extractInheritance( + declaration: ClassDeclaration & ClassDeclarationLike, + ): string | undefined { + if (!declaration.heritageClauses) { + return undefined; + } + + for (const clause of declaration.heritageClauses) { + if (clause.token === ts.SyntaxKind.ExtendsKeyword) { + // We are assuming a single class can only extend one class. + const types = clause.types; + if (types.length > 0) { + const baseClass: ts.ExpressionWithTypeArguments = types[0]; + return baseClass.getText(); + } + } + } + + return undefined; + } + protected extractInterfaceConformance( + declaration: ClassDeclaration & ClassDeclarationLike, + ): string[] { + const implementClause = declaration.heritageClauses?.find( + (clause) => clause.token === ts.SyntaxKind.ImplementsKeyword, + ); + + return implementClause?.types.map((m) => m.getText()) ?? []; + } + /** Gets the tags for a member (protected, readonly, static, etc.) */ protected getMemberTags(member: MethodLike | PropertyLike): MemberTags[] { const tags: MemberTags[] = this.getMemberTagsFromModifiers(member.modifiers ?? []); diff --git a/packages/compiler-cli/src/ngtsc/docs/src/entities.ts b/packages/compiler-cli/src/ngtsc/docs/src/entities.ts index f3c6378374a..c93b5a94d60 100644 --- a/packages/compiler-cli/src/ngtsc/docs/src/entities.ts +++ b/packages/compiler-cli/src/ngtsc/docs/src/entities.ts @@ -107,6 +107,8 @@ export interface ClassEntry extends DocEntry { isAbstract: boolean; members: MemberEntry[]; generics: GenericEntry[]; + extends?: string; + implements: string[]; } // From an API doc perspective, class and interfaces are identical. diff --git a/packages/compiler-cli/test/ngtsc/doc_extraction/class_doc_extraction_spec.ts b/packages/compiler-cli/test/ngtsc/doc_extraction/class_doc_extraction_spec.ts index 606bc1acdd9..6d8a2d8d119 100644 --- a/packages/compiler-cli/test/ngtsc/doc_extraction/class_doc_extraction_spec.ts +++ b/packages/compiler-cli/test/ngtsc/doc_extraction/class_doc_extraction_spec.ts @@ -461,6 +461,27 @@ runInEachFileSystem(() => { expect(genericEntry.default).toBeUndefined(); }); + it('should extract inheritence/interface conformance', () => { + env.write( + 'index.ts', + ` + interface Foo {} + interface Bar {} + + class Parent extends Ancestor {} + + export class Child extends Parent implements Foo, Bar {} + `, + ); + + const docs: DocEntry[] = env.driveDocsExtraction('index.ts'); + expect(docs.length).toBe(1); + + const classEntry = docs[0] as ClassEntry; + expect(classEntry.extends).toBe('Parent'); + expect(classEntry.implements).toEqual(['Foo', 'Bar']); + }); + it('should extract inherited members', () => { env.write( 'index.ts',