angular/adev/shared-docs/pipeline/api-gen/rendering/entities/categorization.mts
Shuaib Hasan Akib 8bdd98ef41 fix(docs-infra): prevent duplicate description rendering for block API entries
Block entries (@if, @defer, @for,@let, @switch) were falling back to the generic
DocsReference template, causing the description to appear twice - once in
the header section and once in the main content area.

This commit adds a dedicated rendering path for block entries:
- Creates BlockEntryRenderable type and associated transforms
- Adds BlockReference template that uses RawHtml directly
- Modifies HeaderApi to accept hideDescription prop
- Updates processing and rendering pipelines to handle blocks

The fix ensures block documentation displays only one description section
while preserving the existing behavior for all other API entry types.

Update adev/shared-docs/pipeline/api-gen/rendering/transforms/block-transforms.mts

Co-authored-by: Matthieu Riegler <kyro38@gmail.com>
2026-01-12 13:37:54 -08:00

201 lines
7.3 KiB
TypeScript

/**
* @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.dev/license
*/
import {
BlockEntry,
ClassEntry,
ConstantEntry,
DecoratorEntry,
DocEntry,
EntryType,
EnumEntry,
FunctionEntry,
InitializerApiFunctionEntry,
InterfaceEntry,
JsDocTagEntry,
MemberEntry,
MemberType,
MethodEntry,
PropertyEntry,
TypeAliasEntry,
} from '../entities.mjs';
import {CliCommand} from '../cli-entities.mjs';
import {
BlockEntryRenderable,
ClassEntryRenderable,
ConstantEntryRenderable,
DecoratorEntryRenderable,
DocEntryRenderable,
EnumEntryRenderable,
FunctionEntryRenderable,
InitializerApiFunctionRenderable,
InterfaceEntryRenderable,
MemberEntryRenderable,
MethodEntryRenderable,
TypeAliasEntryRenderable,
} from './renderables.mjs';
import {HasJsDocTags} from './traits.mjs';
/** Gets whether the given entry represents a class */
export function isClassEntry(entry: DocEntryRenderable): entry is ClassEntryRenderable;
export function isClassEntry(entry: DocEntry): entry is ClassEntry;
export function isClassEntry(entry: DocEntry): entry is ClassEntry {
// TODO: add something like `statementType` to extraction so we don't have to check so many
// entry types here.
return (
entry.entryType === EntryType.UndecoratedClass ||
entry.entryType === EntryType.Component ||
entry.entryType === EntryType.Pipe ||
entry.entryType === EntryType.NgModule ||
entry.entryType === EntryType.Directive
);
}
export function isDecoratorEntry(entry: DocEntryRenderable): entry is DecoratorEntryRenderable;
export function isDecoratorEntry(entry: DocEntry): entry is DecoratorEntry;
export function isDecoratorEntry(entry: DocEntry): entry is DecoratorEntry {
return entry.entryType === EntryType.Decorator;
}
/** Gets whether the given entry represents a constant */
export function isConstantEntry(entry: DocEntryRenderable): entry is ConstantEntryRenderable;
export function isConstantEntry(entry: DocEntry): entry is ConstantEntry;
export function isConstantEntry(entry: DocEntry): entry is ConstantEntry {
return entry.entryType === EntryType.Constant;
}
/** Gets whether the given entry represents a type alias */
export function isTypeAliasEntry(entry: DocEntryRenderable): entry is TypeAliasEntryRenderable;
export function isTypeAliasEntry(entry: DocEntry): entry is TypeAliasEntry;
export function isTypeAliasEntry(entry: DocEntry): entry is TypeAliasEntry {
return entry.entryType === EntryType.TypeAlias;
}
/** Gets whether the given entry represents an enum */
export function isEnumEntry(entry: DocEntryRenderable): entry is EnumEntryRenderable;
export function isEnumEntry(entry: DocEntry): entry is EnumEntry;
export function isEnumEntry(entry: DocEntry): entry is EnumEntry {
return entry.entryType === EntryType.Enum;
}
/** Gets whether the given entry represents an interface. */
export function isInterfaceEntry(
entry: MemberEntryRenderable,
): entry is InterfaceEntryRenderable & MemberEntryRenderable;
export function isInterfaceEntry(entry: DocEntryRenderable): entry is InterfaceEntryRenderable;
export function isInterfaceEntry(entry: DocEntry): entry is InterfaceEntry;
export function isInterfaceEntry(entry: DocEntry | MemberEntryRenderable): entry is InterfaceEntry {
return (entry as DocEntry).entryType === EntryType.Interface;
}
/** Gets whether the given member entry is a method entry. */
export function isClassMethodEntry(entry: MemberEntryRenderable): entry is MethodEntryRenderable;
export function isClassMethodEntry(entry: MemberEntry): entry is MethodEntry;
export function isClassMethodEntry(entry: MemberEntry): entry is MethodEntry {
return entry.memberType === MemberType.Method;
}
/** Gets whether the given entry represents a function */
export function isFunctionEntry(entry: DocEntryRenderable): entry is FunctionEntryRenderable;
export function isFunctionEntry(entry: DocEntry): entry is FunctionEntry;
export function isFunctionEntry(entry: DocEntry): entry is FunctionEntry {
return entry.entryType === EntryType.Function;
}
/** Gets whether the given entry represents a block */
export function isBlockEntry(entry: DocEntryRenderable): entry is BlockEntryRenderable;
export function isBlockEntry(entry: DocEntry): entry is BlockEntry;
export function isBlockEntry(entry: DocEntry): entry is BlockEntry {
return entry.entryType === EntryType.Block;
}
export function isInitializerApiFunctionEntry(
entry: DocEntryRenderable,
): entry is InitializerApiFunctionRenderable;
export function isInitializerApiFunctionEntry(
entry: DocEntry,
): entry is InitializerApiFunctionEntry;
export function isInitializerApiFunctionEntry(
entry: DocEntry,
): entry is InitializerApiFunctionEntry {
return entry.entryType === EntryType.InitializerApiFunction;
}
/** Gets whether the given entry represents a property */
export function isPropertyEntry(entry: MemberEntry): entry is PropertyEntry {
return entry.memberType === MemberType.Property;
}
/** Gets whether the given entry represents a getter */
export function isGetterEntry(entry: MemberEntry): entry is PropertyEntry {
return entry.memberType === MemberType.Getter;
}
/** Gets whether the given entry represents a setter */
export function isSetterEntry(entry: MemberEntry): entry is PropertyEntry {
return entry.memberType === MemberType.Setter;
}
/** Gets whether the given entry is hidden. */
export function isHiddenEntry<T extends HasJsDocTags>(entry: T): boolean {
return getTag(entry, 'docs-private', /* every */ true) ? true : false;
}
/** Gets whether the given entry is deprecated. */
export function isDeprecatedEntry<T extends HasJsDocTags>(entry: T): boolean {
return getTag(entry, 'deprecated', /* every */ true) ? true : false;
}
export function getDeprecatedEntry<T extends HasJsDocTags>(entry: T) {
const comment = entry.jsdocTags.find((tag) => tag.name === 'deprecated')?.comment;
// Dropping the eventual version number in front of the comment.
return comment?.match(/(?:\d+(?:\.\d+)?\s*)?(.*)/s)?.[1] ?? null;
}
/** Gets whether the given entry has a given JsDoc tag. */
function getTag<T extends HasJsDocTags | FunctionEntry>(entry: T, tag: string, every = false) {
const hasTagName = (t: JsDocTagEntry) => t.name === tag;
if (every && 'signatures' in entry && entry.signatures.length > 1) {
// For overloads we need to check all signatures.
return entry.signatures.every((s) => s.jsdocTags.some(hasTagName))
? entry.signatures[0].jsdocTags.find(hasTagName)
: undefined;
}
const jsdocTags = [
...entry.jsdocTags,
...((entry as FunctionEntry).signatures?.flatMap((s) => s.jsdocTags) ?? []),
...((entry as FunctionEntry).implementation?.jsdocTags ?? []),
];
return jsdocTags.find(hasTagName);
}
export function getTagSinceVersion<T extends HasJsDocTags>(
entry: T,
tagName: string,
every = false,
): {version: string | undefined} | undefined {
const tag = getTag(entry, tagName, every);
if (!tag) {
return undefined;
}
// In case of deprecated tag we need to separate the version from the deprecation message.
const version = tag.comment.match(/\d+(\.\d+)?/)?.[0];
return {version};
}
/** Gets whether the given entry is a cli entry. */
export function isCliEntry(entry: unknown): entry is CliCommand {
return (entry as CliCommand).command !== undefined;
}