Skip to content

Commit a7fa253

Browse files
jelbournalxhub
authored andcommitted
feat(compiler): extract api docs for interfaces (#52006)
This adds API doc extraction for interfaces, largely using the same code paths for classes. The primary difference between classes and interfaces is that classes have member _declarations_ while interfaces have member _signatures_. This largely doesn't matter for the purposes of extraction, but the types are distinct with no common base types, so we have to do a fair amount of type unioning and aliasing. PR Close #52006
1 parent dde3fda commit a7fa253

File tree

5 files changed

+287
-28
lines changed

5 files changed

+287
-28
lines changed

packages/compiler-cli/src/ngtsc/docs/src/class_extractor.ts

Lines changed: 61 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,42 @@ import {Reference} from '../../imports';
1414
import {DirectiveMeta, InputMapping, InputOrOutput, MetadataReader, NgModuleMeta, PipeMeta} from '../../metadata';
1515
import {ClassDeclaration} from '../../reflection';
1616

17-
import {ClassEntry, DirectiveEntry, EntryType, MemberEntry, MemberTags, MemberType, MethodEntry, PipeEntry, PropertyEntry} from './entities';
17+
import {ClassEntry, DirectiveEntry, EntryType, InterfaceEntry, MemberEntry, MemberTags, MemberType, MethodEntry, PipeEntry, PropertyEntry} from './entities';
1818
import {extractResolvedTypeString} from './type_extractor';
1919

20+
// For the purpose of extraction, we can largely treat properties and accessors the same.
21+
2022
/** A class member declaration that is *like* a property (including accessors) */
2123
type PropertyDeclarationLike = ts.PropertyDeclaration|ts.AccessorDeclaration;
2224

23-
/** Extractor to pull info for API reference documentation for a TypeScript class. */
25+
// For the purposes of extraction, we can treat interfaces as identical to classes
26+
// with a couple of shorthand types to normalize over the differences between them.
27+
28+
/** Type representing either a class declaration ro an interface declaration. */
29+
type ClassDeclarationLike = ts.ClassDeclaration|ts.InterfaceDeclaration;
30+
31+
/** Type representing either a class member node or an interface member node. */
32+
type MemberElement = ts.ClassElement|ts.TypeElement;
33+
34+
/** Type representing either a class method declaration or an interface method signature. */
35+
type MethodLike = ts.MethodDeclaration|ts.MethodSignature;
36+
37+
/** Type representing either a class property declaration or an interface property signature. */
38+
type PropertyLike = PropertyDeclarationLike|ts.PropertySignature;
39+
40+
/** Extractor to pull info for API reference documentation for a TypeScript class or interface. */
2441
class ClassExtractor {
2542
constructor(
26-
protected declaration: ClassDeclaration&ts.ClassDeclaration,
27-
protected reference: Reference,
43+
protected declaration: ClassDeclaration&ClassDeclarationLike,
2844
protected typeChecker: ts.TypeChecker,
2945
) {}
3046

3147
/** Extract docs info specific to classes. */
3248
extract(): ClassEntry {
3349
return {
34-
name: this.declaration.name!.text,
35-
entryType: EntryType.UndecoratedClass,
50+
name: this.declaration.name.text,
51+
entryType: ts.isInterfaceDeclaration(this.declaration) ? EntryType.Interface :
52+
EntryType.UndecoratedClass,
3653
members: this.extractAllClassMembers(this.declaration),
3754
description: extractJsDocDescription(this.declaration),
3855
jsdocTags: extractJsDocTags(this.declaration),
@@ -41,7 +58,7 @@ class ClassExtractor {
4158
}
4259

4360
/** Extracts doc info for a class's members. */
44-
protected extractAllClassMembers(classDeclaration: ts.ClassDeclaration): MemberEntry[] {
61+
protected extractAllClassMembers(classDeclaration: ClassDeclarationLike): MemberEntry[] {
4562
const members: MemberEntry[] = [];
4663

4764
for (const member of classDeclaration.members) {
@@ -57,10 +74,10 @@ class ClassExtractor {
5774
}
5875

5976
/** Extract docs for a class's members (methods and properties). */
60-
protected extractClassMember(memberDeclaration: ts.ClassElement): MemberEntry|undefined {
61-
if (ts.isMethodDeclaration(memberDeclaration)) {
77+
protected extractClassMember(memberDeclaration: MemberElement): MemberEntry|undefined {
78+
if (this.isMethod(memberDeclaration)) {
6279
return this.extractMethod(memberDeclaration);
63-
} else if (ts.isPropertyDeclaration(memberDeclaration)) {
80+
} else if (this.isProperty(memberDeclaration)) {
6481
return this.extractClassProperty(memberDeclaration);
6582
} else if (ts.isAccessor(memberDeclaration)) {
6683
return this.extractGetterSetter(memberDeclaration);
@@ -72,7 +89,7 @@ class ClassExtractor {
7289
}
7390

7491
/** Extracts docs for a class method. */
75-
protected extractMethod(methodDeclaration: ts.MethodDeclaration): MethodEntry {
92+
protected extractMethod(methodDeclaration: MethodLike): MethodEntry {
7693
const functionExtractor = new FunctionExtractor(methodDeclaration, this.typeChecker);
7794
return {
7895
...functionExtractor.extract(),
@@ -82,7 +99,7 @@ class ClassExtractor {
8299
}
83100

84101
/** Extracts doc info for a property declaration. */
85-
protected extractClassProperty(propertyDeclaration: PropertyDeclarationLike): PropertyEntry {
102+
protected extractClassProperty(propertyDeclaration: PropertyLike): PropertyEntry {
86103
return {
87104
name: propertyDeclaration.name.getText(),
88105
type: extractResolvedTypeString(propertyDeclaration, this.typeChecker),
@@ -102,8 +119,7 @@ class ClassExtractor {
102119
}
103120

104121
/** Gets the tags for a member (protected, readonly, static, etc.) */
105-
protected getMemberTags(member: ts.MethodDeclaration|ts.PropertyDeclaration|
106-
ts.AccessorDeclaration): MemberTags[] {
122+
protected getMemberTags(member: MethodLike|PropertyLike): MemberTags[] {
107123
const tags: MemberTags[] = this.getMemberTagsFromModifiers(member.modifiers ?? []);
108124

109125
if (member.questionToken) {
@@ -144,28 +160,38 @@ class ClassExtractor {
144160
* - The member is neither a method nor property
145161
* - The member is protected
146162
*/
147-
private isMemberExcluded(member: ts.ClassElement): boolean {
163+
private isMemberExcluded(member: MemberElement): boolean {
148164
return !member.name || !this.isDocumentableMember(member) ||
149165
!!member.modifiers?.some(mod => mod.kind === ts.SyntaxKind.PrivateKeyword);
150166
}
151167

152168
/** Gets whether a class member is a method, property, or accessor. */
153-
private isDocumentableMember(member: ts.ClassElement): member is ts.MethodDeclaration
154-
|ts.PropertyDeclaration {
155-
return ts.isMethodDeclaration(member) || ts.isPropertyDeclaration(member) ||
156-
ts.isAccessor(member);
169+
private isDocumentableMember(member: MemberElement): member is MethodLike|PropertyLike {
170+
return this.isMethod(member) || this.isProperty(member) || ts.isAccessor(member);
171+
}
172+
173+
/** Gets whether a member is a property. */
174+
private isProperty(member: MemberElement): member is PropertyLike {
175+
// Classes have declarations, interface have signatures
176+
return ts.isPropertyDeclaration(member) || ts.isPropertySignature(member);
177+
}
178+
179+
/** Gets whether a member is a method. */
180+
private isMethod(member: MemberElement): member is MethodLike {
181+
// Classes have declarations, interface have signatures
182+
return ts.isMethodDeclaration(member) || ts.isMethodSignature(member);
157183
}
158184
}
159185

160186
/** Extractor to pull info for API reference documentation for an Angular directive. */
161187
class DirectiveExtractor extends ClassExtractor {
162188
constructor(
163189
declaration: ClassDeclaration&ts.ClassDeclaration,
164-
reference: Reference,
190+
protected reference: Reference,
165191
protected metadata: DirectiveMeta,
166192
checker: ts.TypeChecker,
167193
) {
168-
super(declaration, reference, checker);
194+
super(declaration, checker);
169195
}
170196

171197
/** Extract docs info for directives and components (including underlying class info). */
@@ -215,11 +241,11 @@ class DirectiveExtractor extends ClassExtractor {
215241
class PipeExtractor extends ClassExtractor {
216242
constructor(
217243
declaration: ClassDeclaration&ts.ClassDeclaration,
218-
reference: Reference,
244+
protected reference: Reference,
219245
private metadata: PipeMeta,
220246
typeChecker: ts.TypeChecker,
221247
) {
222-
super(declaration, reference, typeChecker);
248+
super(declaration, typeChecker);
223249
}
224250

225251
override extract(): PipeEntry {
@@ -236,11 +262,11 @@ class PipeExtractor extends ClassExtractor {
236262
class NgModuleExtractor extends ClassExtractor {
237263
constructor(
238264
declaration: ClassDeclaration&ts.ClassDeclaration,
239-
reference: Reference,
265+
protected reference: Reference,
240266
private metadata: NgModuleMeta,
241267
typeChecker: ts.TypeChecker,
242268
) {
243-
super(declaration, reference, typeChecker);
269+
super(declaration, typeChecker);
244270
}
245271

246272
override extract(): ClassEntry {
@@ -272,8 +298,17 @@ export function extractClass(
272298
} else if (ngModuleMetadata) {
273299
extractor = new NgModuleExtractor(classDeclaration, ref, ngModuleMetadata, typeChecker);
274300
} else {
275-
extractor = new ClassExtractor(classDeclaration, ref, typeChecker);
301+
extractor = new ClassExtractor(classDeclaration, typeChecker);
276302
}
277303

278304
return extractor.extract();
279305
}
306+
307+
/** Extracts documentation info for an interface. */
308+
export function extractInterface(
309+
declaration: ts.InterfaceDeclaration,
310+
typeChecker: ts.TypeChecker,
311+
): InterfaceEntry {
312+
const extractor = new ClassExtractor(declaration, typeChecker);
313+
return extractor.extract();
314+
}

packages/compiler-cli/src/ngtsc/docs/src/entities.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ export interface ClassEntry extends DocEntry {
6666
members: MemberEntry[];
6767
}
6868

69+
// From an API doc perspective, class and interfaces are identical.
70+
71+
/** Documentation entity for a TypeScript interface. */
72+
export type InterfaceEntry = ClassEntry;
73+
6974
/** Documentation entity for a TypeScript enum. */
7075
export interface EnumEntry extends DocEntry {
7176
members: EnumMemberEntry[];

packages/compiler-cli/src/ngtsc/docs/src/extractor.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import ts from 'typescript';
1313
import {MetadataReader} from '../../metadata';
1414
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
1515

16-
import {extractClass} from './class_extractor';
16+
import {extractClass, extractInterface} from './class_extractor';
1717
import {extractConstant, isSyntheticAngularConstant} from './constant_extractor';
1818
import {DocEntry} from './entities';
1919

@@ -54,6 +54,10 @@ export class DocsExtractor {
5454
entry = extractClass(node, this.metadataReader, this.typeChecker);
5555
}
5656

57+
if (ts.isInterfaceDeclaration(node)) {
58+
entry = extractInterface(node, this.typeChecker);
59+
}
60+
5761
if (ts.isFunctionDeclaration(node)) {
5862
const functionExtractor = new FunctionExtractor(node, this.typeChecker);
5963
entry = functionExtractor.extract();

packages/compiler-cli/src/ngtsc/docs/src/function_extractor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {extractResolvedTypeString} from './type_extractor';
1414

1515
export class FunctionExtractor {
1616
constructor(
17-
private declaration: ts.FunctionDeclaration|ts.MethodDeclaration,
17+
private declaration: ts.FunctionDeclaration|ts.MethodDeclaration|ts.MethodSignature,
1818
private typeChecker: ts.TypeChecker,
1919
) {}
2020

0 commit comments

Comments
 (0)