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

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`.
  • Loading branch information
devversion committed Mar 27, 2024
1 parent 6b09094 commit 530f1b0
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 530f1b0

Please sign in to comment.