Skip to content

Commit 98b880b

Browse files
devversionjosephperrott
authored andcommitted
docs: MatMenu api docs are not generated (#16219)
Currently the `MatMenu` API docs are not generated because the `MatMenu` class is no longer treated as directive/component because there is no decorator w/ directive metadata. In order to fix this in a flexible way, a new JSdoc tag has been introduced that allows enforcing the public state of a symbol (with the possibility of having a public symbol name). This allows us to actually expose the real `_MatMenu` class w/ directive metadata in the API docs under the `MatMenu` symbol name. Fixes #16198
1 parent 8daaf4d commit 98b880b

File tree

7 files changed

+123
-76
lines changed

7 files changed

+123
-76
lines changed

src/material/menu/menu.ts

+2
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,7 @@ export class _MatMenuBase implements AfterContentInit, MatMenuPanel<MatMenuItem>
416416
}
417417
}
418418

419+
/** @docs-private We show the "_MatMenu" class as "MatMenu" in the docs. */
419420
export class MatMenu extends _MatMenuBase {}
420421

421422
// Note on the weird inheritance setup: we need three classes, because the MDC-based menu has to
@@ -429,6 +430,7 @@ export class MatMenu extends _MatMenuBase {}
429430
// * _MatMenu - the actual menu component implementation with the Angular metadata that should
430431
// be tree shaken away for MDC.
431432

433+
/** @docs-public MatMenu */
432434
@Component({
433435
moduleId: module.id,
434436
selector: 'mat-menu',

tools/dgeni/bazel-bin.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,11 @@ if (require.main === module) {
101101
// Run the docs generation. The process will be automatically kept alive until Dgeni
102102
// completed. In case the returned promise has been rejected, we need to manually exit the
103103
// process with the proper exit code because Dgeni doesn't use native promises which would
104-
// automatically cause the error to propagate. The error message will be automatically
105-
// printed internally by Dgeni (so we don't want to repeat here)
106-
new Dgeni([apiDocsPackage]).generate().catch(() => process.exit(1));
104+
// automatically cause the error to propagate.
105+
new Dgeni([apiDocsPackage]).generate().catch((e: any) => {
106+
console.error(e);
107+
process.exit(1);
108+
});
107109
}
108110

109111

tools/dgeni/common/dgeni-definitions.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export interface CategorizedClassDoc extends ClassExportDoc, CategorizedClassLik
3333
directiveExportAs?: string | null;
3434
directiveSelectors?: string[];
3535
directiveMetadata: Map<string, any> | null;
36-
extendedDoc: ClassLikeExportDoc | null;
36+
extendedDoc: ClassLikeExportDoc | undefined;
3737
}
3838

3939
/** Extended Dgeni property-member document that includes extracted Angular metadata. */

tools/dgeni/common/private-docs.ts

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import {ApiDoc} from 'dgeni-packages/typescript/api-doc-types/ApiDoc';
2+
import {MemberDoc} from 'dgeni-packages/typescript/api-doc-types/MemberDoc';
3+
4+
const INTERNAL_METHODS = [
5+
// Lifecycle methods
6+
'ngOnInit',
7+
'ngOnChanges',
8+
'ngDoCheck',
9+
'ngAfterContentInit',
10+
'ngAfterContentChecked',
11+
'ngAfterViewInit',
12+
'ngAfterViewChecked',
13+
'ngOnDestroy',
14+
15+
// ControlValueAccessor methods
16+
'writeValue',
17+
'registerOnChange',
18+
'registerOnTouched',
19+
'setDisabledState',
20+
21+
// Don't ever need to document constructors
22+
'constructor',
23+
24+
// tabIndex exists on all elements, no need to document it
25+
'tabIndex',
26+
];
27+
28+
/** Checks whether the given API document is public. */
29+
export function isPublicDoc(doc: ApiDoc) {
30+
if (_isEnforcedPublicDoc(doc)) {
31+
return true;
32+
}
33+
if (_hasDocsPrivateTag(doc) || doc.name.startsWith('_')) {
34+
return false;
35+
} else if (doc instanceof MemberDoc) {
36+
return !_isInternalMember(doc);
37+
}
38+
return true;
39+
}
40+
41+
/** Gets the @docs-public tag from the given document if present. */
42+
export function getDocsPublicTag(doc: any): {tagName: string, description: string}|undefined {
43+
const tags = doc.tags && doc.tags.tags;
44+
return tags ? tags.find((d: any) => d.tagName == 'docs-public') : undefined;
45+
}
46+
47+
/** Whether the given method member is listed as an internal member. */
48+
function _isInternalMember(memberDoc: MemberDoc) {
49+
return INTERNAL_METHODS.includes(memberDoc.name);
50+
}
51+
52+
/** Whether the given doc has a @docs-private tag set. */
53+
function _hasDocsPrivateTag(doc: any) {
54+
const tags = doc.tags && doc.tags.tags;
55+
return tags ? tags.find((d: any) => d.tagName == 'docs-private') : false;
56+
}
57+
58+
/**
59+
* Whether the given doc has the @docs-public tag specified and should be enforced as
60+
* public document. This allows symbols which are usually private to show up in the docs.
61+
*
62+
* Additionally symbols with "@docs-public" tag can specify a public name under which the
63+
* document should show up in the docs. This is useful for cases where a class needs to be
64+
* split up into several base classes to support the MDC prototypes. e.g. "_MatMenu" should
65+
* show up in the docs as "MatMenu".
66+
*/
67+
function _isEnforcedPublicDoc(doc: any): boolean {
68+
return getDocsPublicTag(doc) !== undefined;
69+
}

tools/dgeni/docs-package.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ apiDocsPackage.config(function(computePathsProcessor: any) {
7878
apiDocsPackage.config(function(parseTagsProcessor: any) {
7979
parseTagsProcessor.tagDefinitions = parseTagsProcessor.tagDefinitions.concat([
8080
{name: 'docs-private'},
81+
{name: 'docs-public'},
8182
{name: 'breaking-change'},
8283
]);
8384
});
@@ -89,7 +90,6 @@ apiDocsPackage.config(function(checkAnchorLinksProcessor: any) {
8990

9091
// Configure the processor for understanding TypeScript.
9192
apiDocsPackage.config(function(readTypeScriptModules: ReadTypeScriptModules) {
92-
readTypeScriptModules.ignoreExportsMatching = [/^_/];
9393
readTypeScriptModules.hidePrivateMembers = true;
9494
});
9595

tools/dgeni/processors/categorizer.ts

+23-21
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
} from '../common/dgeni-definitions';
2121
import {getDirectiveMetadata} from '../common/directive-metadata';
2222
import {normalizeFunctionParameters} from '../common/normalize-function-parameters';
23+
import {isPublicDoc} from '../common/private-docs';
2324
import {getInputBindingData, getOutputBindingData} from '../common/property-bindings';
2425
import {sortCategorizedMethodMembers, sortCategorizedPropertyMembers} from '../common/sort-members';
2526

@@ -37,21 +38,16 @@ export class Categorizer implements Processor {
3738
$runBefore = ['docs-processed'];
3839

3940
$process(docs: DocCollection) {
40-
docs
41-
.filter(doc => doc.docType === 'class' || doc.docType === 'interface')
42-
.forEach(doc => this._decorateClassLikeDoc(doc));
41+
docs.filter(doc => doc.docType === 'class' || doc.docType === 'interface')
42+
.forEach(doc => this._decorateClassLikeDoc(doc));
4343

44-
docs
45-
.filter(doc => doc.docType === 'function')
46-
.forEach(doc => this._decorateFunctionExportDoc(doc));
44+
docs.filter(doc => doc.docType === 'function')
45+
.forEach(doc => this._decorateFunctionExportDoc(doc));
4746

48-
docs
49-
.filter(doc => doc.docType === 'const')
50-
.forEach(doc => this._decorateConstExportDoc(doc));
47+
docs.filter(doc => doc.docType === 'const').forEach(doc => this._decorateConstExportDoc(doc));
5148

52-
docs
53-
.filter(doc => doc.docType === 'type-alias')
54-
.forEach(doc => this._decorateTypeAliasExportDoc(doc));
49+
docs.filter(doc => doc.docType === 'type-alias')
50+
.forEach(doc => this._decorateTypeAliasExportDoc(doc));
5551
}
5652

5753
/**
@@ -60,13 +56,12 @@ export class Categorizer implements Processor {
6056
*/
6157
private _decorateClassLikeDoc(classLikeDoc: CategorizedClassLikeDoc) {
6258
// Resolve all methods and properties from the classDoc.
63-
classLikeDoc.methods = classLikeDoc.members
64-
.filter(isMethod)
65-
.filter(filterDuplicateMembers) as CategorizedMethodMemberDoc[];
59+
classLikeDoc.methods = classLikeDoc.members.filter(isMethod).filter(filterDuplicateMembers) as
60+
CategorizedMethodMemberDoc[];
6661

67-
classLikeDoc.properties = classLikeDoc.members
68-
.filter(isProperty)
69-
.filter(filterDuplicateMembers) as CategorizedPropertyMemberDoc[];
62+
classLikeDoc.properties =
63+
classLikeDoc.members.filter(isProperty).filter(filterDuplicateMembers) as
64+
CategorizedPropertyMemberDoc[];
7065

7166
// Special decorations for real class documents that don't apply for interfaces.
7267
if (classLikeDoc.docType === 'class') {
@@ -94,9 +89,16 @@ export class Categorizer implements Processor {
9489
// Classes can only extend a single class. This means that there can't be multiple extend
9590
// clauses for the Dgeni document. To make the template syntax simpler and more readable,
9691
// store the extended class in a variable.
97-
classDoc.extendedDoc = classDoc.extendsClauses[0] ? classDoc.extendsClauses[0].doc! : null;
92+
classDoc.extendedDoc = classDoc.extendsClauses[0] ? classDoc.extendsClauses[0].doc! : undefined;
9893
classDoc.directiveMetadata = getDirectiveMetadata(classDoc);
9994

95+
// In case the extended document is not public, we don't want to print it in the
96+
// rendered class API doc. This causes confusion and also is not helpful as the
97+
// extended document is not part of the docs and cannot be viewed.
98+
if (classDoc.extendedDoc !== undefined && !isPublicDoc(classDoc.extendedDoc)) {
99+
classDoc.extendedDoc = undefined;
100+
}
101+
100102
// Categorize the current visited classDoc into its Angular type.
101103
if (isDirective(classDoc) && classDoc.directiveMetadata) {
102104
classDoc.isDirective = true;
@@ -151,7 +153,8 @@ export class Categorizer implements Processor {
151153
decorateDeprecatedDoc(propertyDoc);
152154

153155
const metadata = propertyDoc.containerDoc.docType === 'class' ?
154-
(propertyDoc.containerDoc as CategorizedClassDoc).directiveMetadata : null;
156+
(propertyDoc.containerDoc as CategorizedClassDoc).directiveMetadata :
157+
null;
155158

156159
const inputMetadata = metadata ? getInputBindingData(propertyDoc, metadata) : null;
157160
const outputMetadata = metadata ? getOutputBindingData(propertyDoc, metadata) : null;
@@ -172,7 +175,6 @@ export class Categorizer implements Processor {
172175

173176
classDoc.methods.forEach((methodDoc, index) => {
174177
if (methodDoc.overloads.length > 0) {
175-
176178
// Add each method overload to the methods that will be shown in the docs.
177179
// Note that we cannot add the overloads immediately to the methods array because
178180
// that would cause the iteration to visit the new overloads.
+22-50
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,7 @@
11
import {DocCollection, Processor} from 'dgeni';
2-
import {ApiDoc} from 'dgeni-packages/typescript/api-doc-types/ApiDoc';
2+
import {BaseApiDoc} from 'dgeni-packages/typescript/api-doc-types/ApiDoc';
33
import {ClassExportDoc} from 'dgeni-packages/typescript/api-doc-types/ClassExportDoc';
4-
import {MemberDoc} from 'dgeni-packages/typescript/api-doc-types/MemberDoc';
5-
6-
const INTERNAL_METHODS = [
7-
// Lifecycle methods
8-
'ngOnInit',
9-
'ngOnChanges',
10-
'ngDoCheck',
11-
'ngAfterContentInit',
12-
'ngAfterContentChecked',
13-
'ngAfterViewInit',
14-
'ngAfterViewChecked',
15-
'ngOnDestroy',
16-
17-
// ControlValueAccessor methods
18-
'writeValue',
19-
'registerOnChange',
20-
'registerOnTouched',
21-
'setDisabledState',
22-
23-
// Don't ever need to document constructors
24-
'constructor',
25-
26-
// tabIndex exists on all elements, no need to document it
27-
'tabIndex',
28-
];
4+
import {getDocsPublicTag, isPublicDoc} from '../common/private-docs';
295

306
/**
317
* Processor to filter out symbols that should not be shown in the Material docs.
@@ -35,29 +11,25 @@ export class DocsPrivateFilter implements Processor {
3511
$runBefore = ['categorizer'];
3612

3713
$process(docs: DocCollection) {
38-
return docs.filter(doc => this._isPublicDoc(doc));
39-
}
40-
41-
/** Marks the given API doc with a property that describes its public state. */
42-
private _isPublicDoc(doc: ApiDoc) {
43-
if (this._hasDocsPrivateTag(doc) || doc.name.startsWith('_')) {
44-
return false;
45-
} else if (doc instanceof MemberDoc) {
46-
return !this._isInternalMember(doc);
47-
} else if (doc instanceof ClassExportDoc) {
48-
doc.members = doc.members.filter(memberDoc => this._isPublicDoc(memberDoc));
49-
}
50-
return true;
51-
}
52-
53-
/** Whether the given method member is listed as an internal member. */
54-
private _isInternalMember(memberDoc: MemberDoc) {
55-
return INTERNAL_METHODS.includes(memberDoc.name);
56-
}
57-
58-
/** Whether the given doc has a @docs-private tag set. */
59-
private _hasDocsPrivateTag(doc: any) {
60-
const tags = doc.tags && doc.tags.tags;
61-
return tags ? tags.find((d: any) => d.tagName == 'docs-private') : false;
14+
return docs.filter(doc => {
15+
const isPublic = isPublicDoc(doc);
16+
17+
// Update the API document name in case the "@docs-public" tag is used
18+
// with an alias name.
19+
if (isPublic && doc instanceof BaseApiDoc) {
20+
const docsPublicTag = getDocsPublicTag(doc);
21+
if (docsPublicTag !== undefined && docsPublicTag.description) {
22+
doc.name = docsPublicTag.description;
23+
}
24+
}
25+
26+
// Filter out private class members which could be annotated
27+
// with the "@docs-private" tag.
28+
if (isPublic && doc instanceof ClassExportDoc) {
29+
doc.members = doc.members.filter(memberDoc => isPublicDoc(memberDoc));
30+
}
31+
32+
return isPublic;
33+
});
6234
}
6335
}

0 commit comments

Comments
 (0)