From ee76001431946b15906a954ae53e080bcdb5e341 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Mon, 18 Mar 2024 16:16:41 +0000 Subject: [PATCH] refactor(compiler-cli): support ignoring specific doc entries during extraction (#55053) This commit adds support for ignoring specific doc entries when extracting doc entries. This allows us to drop e.g. `InputFunction` from the API docs, given that the `input` API entry holds all the relevant information. `InputFunction` only exists for type purposes in the `.d.ts`. PR Close #55053 --- .../src/ngtsc/docs/src/extractor.ts | 31 +++++++-- .../ngtsc/doc_extraction/docs_private_spec.ts | 63 +++++++++++++++++++ packages/core/src/authoring/input/input.ts | 1 + packages/core/src/authoring/model/model.ts | 1 + packages/core/src/authoring/queries.ts | 2 + 5 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 packages/compiler-cli/test/ngtsc/doc_extraction/docs_private_spec.ts diff --git a/packages/compiler-cli/src/ngtsc/docs/src/extractor.ts b/packages/compiler-cli/src/ngtsc/docs/src/extractor.ts index e8bcdfb4906..c662968623a 100644 --- a/packages/compiler-cli/src/ngtsc/docs/src/extractor.ts +++ b/packages/compiler-cli/src/ngtsc/docs/src/extractor.ts @@ -42,10 +42,12 @@ export class DocsExtractor { const exportedDeclarations = this.getExportedDeclarations(sourceFile); for (const [exportName, node] of exportedDeclarations) { // Skip any symbols with an Angular-internal name. - if (isAngularPrivateName(exportName)) continue; + if (isAngularPrivateName(exportName)) { + continue; + } const entry = this.extractDeclaration(node); - if (entry) { + if (entry && !isIgnoredDocEntry(entry)) { // The exported name of an API may be different from its declaration name, so // use the declaration name. entries.push({...entry, name: exportName}); @@ -77,10 +79,8 @@ export class DocsExtractor { } if (ts.isVariableDeclaration(node) && !isSyntheticAngularConstant(node)) { - if (isDecoratorDeclaration(node)) { - return extractorDecorator(node, this.typeChecker); - } - return extractConstant(node, this.typeChecker); + return isDecoratorDeclaration(node) ? extractorDecorator(node, this.typeChecker) : + extractConstant(node, this.typeChecker); } if (ts.isTypeAliasDeclaration(node)) { @@ -138,3 +138,22 @@ function isIgnoredInterface(node: ts.InterfaceDeclaration) { // that contain the decorator options. return node.name.getText().endsWith('Decorator') || isDecoratorOptionsInterface(node); } + +/** + * Whether the doc entry should be ignored. + * + * Note: We cannot check whether a node is marked as docs private + * before extraction because the extractor may find the attached + * JSDoc tags on different AST nodes. For example, a variable declaration + * never has JSDoc tags attached, but rather the parent variable statement. + */ +function isIgnoredDocEntry(entry: DocEntry): boolean { + const isDocsPrivate = entry.jsdocTags.find(e => e.name === 'docsPrivate'); + if (isDocsPrivate !== undefined && isDocsPrivate.comment === '') { + throw new Error( + `Docs extraction: Entry "${entry.name}" is marked as ` + + `"@docsPrivate" but without reasoning.`); + } + + return isDocsPrivate !== undefined; +} diff --git a/packages/compiler-cli/test/ngtsc/doc_extraction/docs_private_spec.ts b/packages/compiler-cli/test/ngtsc/doc_extraction/docs_private_spec.ts new file mode 100644 index 00000000000..e79596180bd --- /dev/null +++ b/packages/compiler-cli/test/ngtsc/doc_extraction/docs_private_spec.ts @@ -0,0 +1,63 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {DocEntry} from '@angular/compiler-cli/src/ngtsc/docs/src/entities'; +import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing'; + +import {NgtscTestEnvironment} from '../env'; + +runInEachFileSystem(() => { + describe('ngtsc docs: @docsPrivate tag', () => { + let env: NgtscTestEnvironment; + + beforeEach(() => { + env = NgtscTestEnvironment.setup({}); + env.tsconfig(); + }); + + function test(input: string): DocEntry[] { + env.write('index.ts', input); + return env.driveDocsExtraction('index.ts'); + } + + it('should omit constant annotated with `@docsPrivate`', () => { + expect(test(` + /** @docsPrivate */ + export const bla = true; + `)).toEqual([]); + }); + + it('should omit class annotated with `@docsPrivate`', () => { + expect(test(` + /** @docsPrivate */ + export class Bla {} + `)).toEqual([]); + }); + + it('should omit function annotated with `@docsPrivate`', () => { + expect(test(` + /** @docsPrivate */ + export function bla() {}; + `)).toEqual([]); + }); + + it('should omit interface annotated with `@docsPrivate`', () => { + expect(test(` + /** @docsPrivate */ + export interface BlaFunction {} + `)).toEqual([]); + }); + + it('should error if marked as private without reasoning', () => { + expect(() => test(` + /** @docsPrivate */ + export interface BlaFunction {} + `)).toThrowError(/Entry "BlaFunction" is marked as "@docsPrivate" but without reasoning./); + }); + }); +}); diff --git a/packages/core/src/authoring/input/input.ts b/packages/core/src/authoring/input/input.ts index b177d8aee4b..d4b5798bd8f 100644 --- a/packages/core/src/authoring/input/input.ts +++ b/packages/core/src/authoring/input/input.ts @@ -32,6 +32,7 @@ export function inputRequiredFunction(opts?: InputOptions * `input.required` function. * * @developerPreview + * @docsPrivate Ignored because `input` is the canonical API entry. */ export interface InputFunction { /** diff --git a/packages/core/src/authoring/model/model.ts b/packages/core/src/authoring/model/model.ts index ef7467faf8f..6b5ff34dd3d 100644 --- a/packages/core/src/authoring/model/model.ts +++ b/packages/core/src/authoring/model/model.ts @@ -32,6 +32,7 @@ export function modelRequiredFunction(): ModelSignal { * `model.required` function. * * @developerPreview + * @docsPrivate Ignored because `model` is the canonical API entry. */ export interface ModelFunction { /** diff --git a/packages/core/src/authoring/queries.ts b/packages/core/src/authoring/queries.ts index 66e050ddf38..87dc2a6cabb 100644 --- a/packages/core/src/authoring/queries.ts +++ b/packages/core/src/authoring/queries.ts @@ -31,6 +31,7 @@ function viewChildRequiredFn( * property. * * @developerPreview + * @docsPrivate Ignored because `viewChild` is the canonical API entry. */ export interface ViewChildFunction { /** @@ -140,6 +141,7 @@ function contentChildRequiredFn( * provides access to required query results via the `.required` property. * * @developerPreview + * @docsPrivate Ignored because `contentChild` is the canonical API entry. */ export interface ContentChildFunction { /**