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
This commit is contained in:
Paul Gschwendtner 2024-03-18 16:16:41 +00:00 committed by Dylan Hunn
parent b63afb9e93
commit ee76001431
5 changed files with 92 additions and 6 deletions

View file

@ -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;
}

View file

@ -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 <reason> */
export const bla = true;
`)).toEqual([]);
});
it('should omit class annotated with `@docsPrivate`', () => {
expect(test(`
/** @docsPrivate <reason> */
export class Bla {}
`)).toEqual([]);
});
it('should omit function annotated with `@docsPrivate`', () => {
expect(test(`
/** @docsPrivate <reason> */
export function bla() {};
`)).toEqual([]);
});
it('should omit interface annotated with `@docsPrivate`', () => {
expect(test(`
/** @docsPrivate <reason> */
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./);
});
});
});

View file

@ -32,6 +32,7 @@ export function inputRequiredFunction<ReadT, WriteT = ReadT>(opts?: InputOptions
* `input.required` function.
*
* @developerPreview
* @docsPrivate Ignored because `input` is the canonical API entry.
*/
export interface InputFunction {
/**

View file

@ -32,6 +32,7 @@ export function modelRequiredFunction<T>(): ModelSignal<T> {
* `model.required` function.
*
* @developerPreview
* @docsPrivate Ignored because `model` is the canonical API entry.
*/
export interface ModelFunction {
/**

View file

@ -31,6 +31,7 @@ function viewChildRequiredFn<LocatorT, ReadT>(
* property.
*
* @developerPreview
* @docsPrivate Ignored because `viewChild` is the canonical API entry.
*/
export interface ViewChildFunction {
/**
@ -140,6 +141,7 @@ function contentChildRequiredFn<LocatorT, ReadT>(
* provides access to required query results via the `.required` property.
*
* @developerPreview
* @docsPrivate Ignored because `contentChild` is the canonical API entry.
*/
export interface ContentChildFunction {
/**