diff --git a/src/material/menu/menu.ts b/src/material/menu/menu.ts index fb4e65d03a8..da74ebe4703 100644 --- a/src/material/menu/menu.ts +++ b/src/material/menu/menu.ts @@ -416,6 +416,7 @@ export class _MatMenuBase implements AfterContentInit, MatMenuPanel } } +/** @docs-private We show the "_MatMenu" class as "MatMenu" in the docs. */ export class MatMenu extends _MatMenuBase {} // 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 {} // * _MatMenu - the actual menu component implementation with the Angular metadata that should // be tree shaken away for MDC. +/** @docs-public MatMenu */ @Component({ moduleId: module.id, selector: 'mat-menu', diff --git a/tools/dgeni/bazel-bin.ts b/tools/dgeni/bazel-bin.ts index 14483837c91..09f656ac5ad 100644 --- a/tools/dgeni/bazel-bin.ts +++ b/tools/dgeni/bazel-bin.ts @@ -101,9 +101,11 @@ if (require.main === module) { // Run the docs generation. The process will be automatically kept alive until Dgeni // completed. In case the returned promise has been rejected, we need to manually exit the // process with the proper exit code because Dgeni doesn't use native promises which would - // automatically cause the error to propagate. The error message will be automatically - // printed internally by Dgeni (so we don't want to repeat here) - new Dgeni([apiDocsPackage]).generate().catch(() => process.exit(1)); + // automatically cause the error to propagate. + new Dgeni([apiDocsPackage]).generate().catch((e: any) => { + console.error(e); + process.exit(1); + }); } diff --git a/tools/dgeni/common/dgeni-definitions.ts b/tools/dgeni/common/dgeni-definitions.ts index 218a0183843..e3f806ab676 100644 --- a/tools/dgeni/common/dgeni-definitions.ts +++ b/tools/dgeni/common/dgeni-definitions.ts @@ -33,7 +33,7 @@ export interface CategorizedClassDoc extends ClassExportDoc, CategorizedClassLik directiveExportAs?: string | null; directiveSelectors?: string[]; directiveMetadata: Map | null; - extendedDoc: ClassLikeExportDoc | null; + extendedDoc: ClassLikeExportDoc | undefined; } /** Extended Dgeni property-member document that includes extracted Angular metadata. */ diff --git a/tools/dgeni/common/private-docs.ts b/tools/dgeni/common/private-docs.ts new file mode 100644 index 00000000000..bcc19bd9366 --- /dev/null +++ b/tools/dgeni/common/private-docs.ts @@ -0,0 +1,69 @@ +import {ApiDoc} from 'dgeni-packages/typescript/api-doc-types/ApiDoc'; +import {MemberDoc} from 'dgeni-packages/typescript/api-doc-types/MemberDoc'; + +const INTERNAL_METHODS = [ + // Lifecycle methods + 'ngOnInit', + 'ngOnChanges', + 'ngDoCheck', + 'ngAfterContentInit', + 'ngAfterContentChecked', + 'ngAfterViewInit', + 'ngAfterViewChecked', + 'ngOnDestroy', + + // ControlValueAccessor methods + 'writeValue', + 'registerOnChange', + 'registerOnTouched', + 'setDisabledState', + + // Don't ever need to document constructors + 'constructor', + + // tabIndex exists on all elements, no need to document it + 'tabIndex', +]; + +/** Checks whether the given API document is public. */ +export function isPublicDoc(doc: ApiDoc) { + if (_isEnforcedPublicDoc(doc)) { + return true; + } + if (_hasDocsPrivateTag(doc) || doc.name.startsWith('_')) { + return false; + } else if (doc instanceof MemberDoc) { + return !_isInternalMember(doc); + } + return true; +} + +/** Gets the @docs-public tag from the given document if present. */ +export function getDocsPublicTag(doc: any): {tagName: string, description: string}|undefined { + const tags = doc.tags && doc.tags.tags; + return tags ? tags.find((d: any) => d.tagName == 'docs-public') : undefined; +} + +/** Whether the given method member is listed as an internal member. */ +function _isInternalMember(memberDoc: MemberDoc) { + return INTERNAL_METHODS.includes(memberDoc.name); +} + +/** Whether the given doc has a @docs-private tag set. */ +function _hasDocsPrivateTag(doc: any) { + const tags = doc.tags && doc.tags.tags; + return tags ? tags.find((d: any) => d.tagName == 'docs-private') : false; +} + +/** + * Whether the given doc has the @docs-public tag specified and should be enforced as + * public document. This allows symbols which are usually private to show up in the docs. + * + * Additionally symbols with "@docs-public" tag can specify a public name under which the + * document should show up in the docs. This is useful for cases where a class needs to be + * split up into several base classes to support the MDC prototypes. e.g. "_MatMenu" should + * show up in the docs as "MatMenu". + */ +function _isEnforcedPublicDoc(doc: any): boolean { + return getDocsPublicTag(doc) !== undefined; +} diff --git a/tools/dgeni/docs-package.ts b/tools/dgeni/docs-package.ts index c95c3072202..f88e356778f 100644 --- a/tools/dgeni/docs-package.ts +++ b/tools/dgeni/docs-package.ts @@ -78,6 +78,7 @@ apiDocsPackage.config(function(computePathsProcessor: any) { apiDocsPackage.config(function(parseTagsProcessor: any) { parseTagsProcessor.tagDefinitions = parseTagsProcessor.tagDefinitions.concat([ {name: 'docs-private'}, + {name: 'docs-public'}, {name: 'breaking-change'}, ]); }); @@ -89,7 +90,6 @@ apiDocsPackage.config(function(checkAnchorLinksProcessor: any) { // Configure the processor for understanding TypeScript. apiDocsPackage.config(function(readTypeScriptModules: ReadTypeScriptModules) { - readTypeScriptModules.ignoreExportsMatching = [/^_/]; readTypeScriptModules.hidePrivateMembers = true; }); diff --git a/tools/dgeni/processors/categorizer.ts b/tools/dgeni/processors/categorizer.ts index 49cf3b3535c..d1b4c25459a 100644 --- a/tools/dgeni/processors/categorizer.ts +++ b/tools/dgeni/processors/categorizer.ts @@ -20,6 +20,7 @@ import { } from '../common/dgeni-definitions'; import {getDirectiveMetadata} from '../common/directive-metadata'; import {normalizeFunctionParameters} from '../common/normalize-function-parameters'; +import {isPublicDoc} from '../common/private-docs'; import {getInputBindingData, getOutputBindingData} from '../common/property-bindings'; import {sortCategorizedMethodMembers, sortCategorizedPropertyMembers} from '../common/sort-members'; @@ -37,21 +38,16 @@ export class Categorizer implements Processor { $runBefore = ['docs-processed']; $process(docs: DocCollection) { - docs - .filter(doc => doc.docType === 'class' || doc.docType === 'interface') - .forEach(doc => this._decorateClassLikeDoc(doc)); + docs.filter(doc => doc.docType === 'class' || doc.docType === 'interface') + .forEach(doc => this._decorateClassLikeDoc(doc)); - docs - .filter(doc => doc.docType === 'function') - .forEach(doc => this._decorateFunctionExportDoc(doc)); + docs.filter(doc => doc.docType === 'function') + .forEach(doc => this._decorateFunctionExportDoc(doc)); - docs - .filter(doc => doc.docType === 'const') - .forEach(doc => this._decorateConstExportDoc(doc)); + docs.filter(doc => doc.docType === 'const').forEach(doc => this._decorateConstExportDoc(doc)); - docs - .filter(doc => doc.docType === 'type-alias') - .forEach(doc => this._decorateTypeAliasExportDoc(doc)); + docs.filter(doc => doc.docType === 'type-alias') + .forEach(doc => this._decorateTypeAliasExportDoc(doc)); } /** @@ -60,13 +56,12 @@ export class Categorizer implements Processor { */ private _decorateClassLikeDoc(classLikeDoc: CategorizedClassLikeDoc) { // Resolve all methods and properties from the classDoc. - classLikeDoc.methods = classLikeDoc.members - .filter(isMethod) - .filter(filterDuplicateMembers) as CategorizedMethodMemberDoc[]; + classLikeDoc.methods = classLikeDoc.members.filter(isMethod).filter(filterDuplicateMembers) as + CategorizedMethodMemberDoc[]; - classLikeDoc.properties = classLikeDoc.members - .filter(isProperty) - .filter(filterDuplicateMembers) as CategorizedPropertyMemberDoc[]; + classLikeDoc.properties = + classLikeDoc.members.filter(isProperty).filter(filterDuplicateMembers) as + CategorizedPropertyMemberDoc[]; // Special decorations for real class documents that don't apply for interfaces. if (classLikeDoc.docType === 'class') { @@ -94,9 +89,16 @@ export class Categorizer implements Processor { // Classes can only extend a single class. This means that there can't be multiple extend // clauses for the Dgeni document. To make the template syntax simpler and more readable, // store the extended class in a variable. - classDoc.extendedDoc = classDoc.extendsClauses[0] ? classDoc.extendsClauses[0].doc! : null; + classDoc.extendedDoc = classDoc.extendsClauses[0] ? classDoc.extendsClauses[0].doc! : undefined; classDoc.directiveMetadata = getDirectiveMetadata(classDoc); + // In case the extended document is not public, we don't want to print it in the + // rendered class API doc. This causes confusion and also is not helpful as the + // extended document is not part of the docs and cannot be viewed. + if (classDoc.extendedDoc !== undefined && !isPublicDoc(classDoc.extendedDoc)) { + classDoc.extendedDoc = undefined; + } + // Categorize the current visited classDoc into its Angular type. if (isDirective(classDoc) && classDoc.directiveMetadata) { classDoc.isDirective = true; @@ -151,7 +153,8 @@ export class Categorizer implements Processor { decorateDeprecatedDoc(propertyDoc); const metadata = propertyDoc.containerDoc.docType === 'class' ? - (propertyDoc.containerDoc as CategorizedClassDoc).directiveMetadata : null; + (propertyDoc.containerDoc as CategorizedClassDoc).directiveMetadata : + null; const inputMetadata = metadata ? getInputBindingData(propertyDoc, metadata) : null; const outputMetadata = metadata ? getOutputBindingData(propertyDoc, metadata) : null; @@ -172,7 +175,6 @@ export class Categorizer implements Processor { classDoc.methods.forEach((methodDoc, index) => { if (methodDoc.overloads.length > 0) { - // Add each method overload to the methods that will be shown in the docs. // Note that we cannot add the overloads immediately to the methods array because // that would cause the iteration to visit the new overloads. diff --git a/tools/dgeni/processors/docs-private-filter.ts b/tools/dgeni/processors/docs-private-filter.ts index 2dbc39f7bcd..279f41c3037 100644 --- a/tools/dgeni/processors/docs-private-filter.ts +++ b/tools/dgeni/processors/docs-private-filter.ts @@ -1,31 +1,7 @@ import {DocCollection, Processor} from 'dgeni'; -import {ApiDoc} from 'dgeni-packages/typescript/api-doc-types/ApiDoc'; +import {BaseApiDoc} from 'dgeni-packages/typescript/api-doc-types/ApiDoc'; import {ClassExportDoc} from 'dgeni-packages/typescript/api-doc-types/ClassExportDoc'; -import {MemberDoc} from 'dgeni-packages/typescript/api-doc-types/MemberDoc'; - -const INTERNAL_METHODS = [ - // Lifecycle methods - 'ngOnInit', - 'ngOnChanges', - 'ngDoCheck', - 'ngAfterContentInit', - 'ngAfterContentChecked', - 'ngAfterViewInit', - 'ngAfterViewChecked', - 'ngOnDestroy', - - // ControlValueAccessor methods - 'writeValue', - 'registerOnChange', - 'registerOnTouched', - 'setDisabledState', - - // Don't ever need to document constructors - 'constructor', - - // tabIndex exists on all elements, no need to document it - 'tabIndex', -]; +import {getDocsPublicTag, isPublicDoc} from '../common/private-docs'; /** * Processor to filter out symbols that should not be shown in the Material docs. @@ -35,29 +11,25 @@ export class DocsPrivateFilter implements Processor { $runBefore = ['categorizer']; $process(docs: DocCollection) { - return docs.filter(doc => this._isPublicDoc(doc)); - } + return docs.filter(doc => { + const isPublic = isPublicDoc(doc); - /** Marks the given API doc with a property that describes its public state. */ - private _isPublicDoc(doc: ApiDoc) { - if (this._hasDocsPrivateTag(doc) || doc.name.startsWith('_')) { - return false; - } else if (doc instanceof MemberDoc) { - return !this._isInternalMember(doc); - } else if (doc instanceof ClassExportDoc) { - doc.members = doc.members.filter(memberDoc => this._isPublicDoc(memberDoc)); - } - return true; - } + // Update the API document name in case the "@docs-public" tag is used + // with an alias name. + if (isPublic && doc instanceof BaseApiDoc) { + const docsPublicTag = getDocsPublicTag(doc); + if (docsPublicTag !== undefined && docsPublicTag.description) { + doc.name = docsPublicTag.description; + } + } - /** Whether the given method member is listed as an internal member. */ - private _isInternalMember(memberDoc: MemberDoc) { - return INTERNAL_METHODS.includes(memberDoc.name); - } + // Filter out private class members which could be annotated + // with the "@docs-private" tag. + if (isPublic && doc instanceof ClassExportDoc) { + doc.members = doc.members.filter(memberDoc => isPublicDoc(memberDoc)); + } - /** Whether the given doc has a @docs-private tag set. */ - private _hasDocsPrivateTag(doc: any) { - const tags = doc.tags && doc.tags.tags; - return tags ? tags.find((d: any) => d.tagName == 'docs-private') : false; + return isPublic; + }); } }