Skip to content

Commit

Permalink
refactor(compiler-cli): support ignoring specific doc entries during …
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
devversion authored and dylhunn committed Mar 28, 2024
1 parent b63afb9 commit ee76001
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 6 deletions.
31 changes: 25 additions & 6 deletions packages/compiler-cli/src/ngtsc/docs/src/extractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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});
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
@@ -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./);
});
});
});
1 change: 1 addition & 0 deletions packages/core/src/authoring/input/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
/**
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/authoring/model/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
/**
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/authoring/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ function viewChildRequiredFn<LocatorT, ReadT>(
* property.
*
* @developerPreview
* @docsPrivate Ignored because `viewChild` is the canonical API entry.
*/
export interface ViewChildFunction {
/**
Expand Down Expand Up @@ -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 {
/**
Expand Down

0 comments on commit ee76001

Please sign in to comment.