From 872853fbcb6be0fa044c725bc1241c025e63285e Mon Sep 17 00:00:00 2001 From: Matthieu Riegler Date: Mon, 18 May 2026 11:18:26 +0200 Subject: [PATCH] docs(docs-infra): Show function args With this change non-overloaded functions also show the params + return type in a dedicated block. --- .../rendering/entities/renderables.mts | 2 +- .../rendering/templates/class-method-info.tsx | 8 +- .../templates/function-reference.tsx | 73 +++++++++++-------- .../api-gen/rendering/templates/parameter.tsx | 6 +- .../api-gen/rendering/test/fake-entries.json | 2 + .../transforms/function-transforms.mts | 14 ++-- .../rendering/transforms/jsdoc-transforms.mts | 2 +- .../transforms/params-transforms.mts | 12 ++- adev/shared-docs/styles/_reference.scss | 7 +- .../src/ngtsc/docs/src/function_extractor.ts | 26 ++++--- 10 files changed, 98 insertions(+), 54 deletions(-) diff --git a/adev/shared-docs/pipeline/api-gen/rendering/entities/renderables.mts b/adev/shared-docs/pipeline/api-gen/rendering/entities/renderables.mts index 13b7f61b9ed..7aaed62791d 100644 --- a/adev/shared-docs/pipeline/api-gen/rendering/entities/renderables.mts +++ b/adev/shared-docs/pipeline/api-gen/rendering/entities/renderables.mts @@ -21,7 +21,6 @@ import { ParameterEntry, PipeEntry, TypeAliasEntry, - EntryType, } from '../entities.mjs'; import {CliCommand, CliOption} from '../cli-entities.mjs'; @@ -105,6 +104,7 @@ export type FunctionEntryRenderable = FunctionEntry & export type FunctionSignatureMetadataRenderable = FunctionSignatureMetadata & DocEntryRenderable & { params: ParameterEntryRenderable[]; + htmlReturnDescription?: string; }; /** Documentation entity for a block augmented with transformed content for rendering. */ diff --git a/adev/shared-docs/pipeline/api-gen/rendering/templates/class-method-info.tsx b/adev/shared-docs/pipeline/api-gen/rendering/templates/class-method-info.tsx index e64dd38b4b3..ac11d390006 100644 --- a/adev/shared-docs/pipeline/api-gen/rendering/templates/class-method-info.tsx +++ b/adev/shared-docs/pipeline/api-gen/rendering/templates/class-method-info.tsx @@ -23,6 +23,7 @@ import {RawHtml} from './raw-html'; export function ClassMethodInfo(props: { entry: FunctionSignatureMetadataRenderable; hideUsageNotes?: boolean; + hideDescription?: boolean; }) { const entry = props.entry; @@ -30,7 +31,9 @@ export function ClassMethodInfo(props: {
- + {!props.hideDescription && ( + + )} {/* In case when method is overloaded we need to indicate which overload is deprecated */} {entry.deprecated ? (
@@ -45,6 +48,9 @@ export function ClassMethodInfo(props: {
@returns + {entry.htmlReturnDescription && ( + + )}
{entry.htmlUsageNotes && !props.hideUsageNotes ? (
diff --git a/adev/shared-docs/pipeline/api-gen/rendering/templates/function-reference.tsx b/adev/shared-docs/pipeline/api-gen/rendering/templates/function-reference.tsx index 408f9baa769..0eae38af743 100644 --- a/adev/shared-docs/pipeline/api-gen/rendering/templates/function-reference.tsx +++ b/adev/shared-docs/pipeline/api-gen/rendering/templates/function-reference.tsx @@ -32,31 +32,43 @@ import {SectionUsageNotes} from './section-usage-notes'; export const signatureCard = ( name: string, signature: FunctionSignatureMetadataRenderable, - opts: {id: string; printSignaturesAsHeader: boolean; hideUsageNotes?: boolean}, + opts: { + id: string; + printSignaturesAsHeader: boolean; + hideUsageNotes?: boolean; + hideHeader?: boolean; + hideDescription?: boolean; + }, ) => { return (
-
- {opts.printSignaturesAsHeader ? ( - - ) : ( - <> -

{name}

-
- -
- - )} -
+ {!opts.hideHeader && ( +
+ {opts.printSignaturesAsHeader ? ( + + ) : ( + <> +

{name}

+
+ +
+ + )} +
+ )}
- +
); @@ -64,8 +76,8 @@ export const signatureCard = ( /** Component to render a function API reference document. */ export function FunctionReference(entry: FunctionEntryRenderable) { - // Use signatures as header if there are multiple signatures. const printSignaturesAsHeader = entry.signatures.length > 1; + const hideSignatureCardDescription = !printSignaturesAsHeader; return (
@@ -73,14 +85,15 @@ export function FunctionReference(entry: FunctionEntryRenderable) {
- {entry.signatures.length > 1 && - entry.signatures.map((s, i) => - signatureCard(s.name, getFunctionMetadataRenderable(s, entry.moduleName, entry.repo), { - id: `${s.name}_${i}`, - printSignaturesAsHeader, - hideUsageNotes: true, - }), - )} + {entry.signatures.map((s, i) => + signatureCard(s.name, getFunctionMetadataRenderable(s, entry.moduleName, entry.repo), { + id: `${s.name}_${i}`, + printSignaturesAsHeader, + hideHeader: !printSignaturesAsHeader, + hideUsageNotes: hideSignatureCardDescription, + hideDescription: hideSignatureCardDescription, + }), + )}
diff --git a/adev/shared-docs/pipeline/api-gen/rendering/templates/parameter.tsx b/adev/shared-docs/pipeline/api-gen/rendering/templates/parameter.tsx index 0c8f5356ffa..db717533dc4 100644 --- a/adev/shared-docs/pipeline/api-gen/rendering/templates/parameter.tsx +++ b/adev/shared-docs/pipeline/api-gen/rendering/templates/parameter.tsx @@ -8,9 +8,9 @@ import {h} from 'preact'; import {ParameterEntryRenderable} from '../entities/renderables.mjs'; -import {RawHtml} from './raw-html'; import {PARAM_GROUP_CLASS_NAME} from '../styling/css-classes.mjs'; import {CodeSymbol} from './code-symbols'; +import {RawHtml} from './raw-html'; /** Component to render a function or method parameter reference doc fragment. */ export function Parameter(props: {param: ParameterEntryRenderable}) { @@ -21,7 +21,9 @@ export function Parameter(props: {param: ParameterEntryRenderable}) { {/*TODO: isOptional, isRestParam*/} @param {param.name} - + + +
); diff --git a/adev/shared-docs/pipeline/api-gen/rendering/test/fake-entries.json b/adev/shared-docs/pipeline/api-gen/rendering/test/fake-entries.json index f1e5c230754..7f75e5c52a3 100644 --- a/adev/shared-docs/pipeline/api-gen/rendering/test/fake-entries.json +++ b/adev/shared-docs/pipeline/api-gen/rendering/test/fake-entries.json @@ -463,6 +463,7 @@ "isRestParam": false } ], + "returnDescription": "A reference that can be used to unregister callbacks registered by this call.", "rawComment": "/**\n * Register callbacks to be invoked the next time the application finishes rendering, during the\n * specified phases. The available phases are:\n * - `earlyRead`\n * Use this phase to **read** from the DOM before a subsequent `write` callback, for example to\n * perform custom layout that the browser doesn't natively support. Prefer the `read` phase if\n * reading can wait until after the write phase. **Never** write to the DOM in this phase.\n * - `write`\n * Use this phase to **write** to the DOM. **Never** read from the DOM in this phase.\n * - `mixedReadWrite`\n * Use this phase to read from and write to the DOM simultaneously. **Never** use this phase if\n * it is possible to divide the work among the other phases instead.\n * - `read`\n * Use this phase to **read** from the DOM. **Never** write to the DOM in this phase.\n *\n *
\n *\n * You should prefer using the `read` and `write` phases over the `earlyRead` and `mixedReadWrite`\n * phases when possible, to avoid performance degradation.\n *\n *
\n *\n * Note that:\n * - Callbacks run in the following phase order *once, after the next render*:\n * 1. `earlyRead`\n * 2. `write`\n * 3. `mixedReadWrite`\n * 4. `read`\n * - Callbacks in the same phase run in the order they are registered.\n * - Callbacks run on browser platforms only, they will not run on the server.\n *\n * The first phase callback to run as part of this spec will receive no parameters. Each\n * subsequent phase callback in this spec will receive the return value of the previously run\n * phase callback as a parameter. This can be used to coordinate work across multiple phases.\n *\n * Angular is unable to verify or enforce that phases are used correctly, and instead\n * relies on each developer to follow the guidelines documented for each value and\n * carefully choose the appropriate one, refactoring their code if necessary. By doing\n * so, Angular is better able to minimize the performance degradation associated with\n * manual DOM access, ensuring the best experience for the end users of your application\n * or library.\n *\n *
\n *\n * Components are not guaranteed to be [hydrated](guide/hydration) before the callback runs.\n * You must use caution when directly reading or writing the DOM and layout.\n *\n *
\n *\n * @param spec The callback functions to register\n * @param options Options to control the behavior of the callback\n *\n * @usageNotes\n *\n * Use `afterNextRender` to read or write the DOM once,\n * for example to initialize a non-Angular library.\n *\n * ### Example\n * ```angular-ts\n * @Component({\n * selector: 'my-chart-cmp',\n * template: `
{{ ... }}
`,\n * })\n * export class MyChartCmp {\n * @ViewChild('chart') chartRef: ElementRef;\n * chart: MyChart|null;\n *\n * constructor() {\n * afterNextRender({\n * write: () => {\n * this.chart = new MyChart(this.chartRef.nativeElement);\n * }\n * });\n * }\n * }\n * ```\n *\n * @developerPreview\n */", "returnType": "AfterRenderRef" }, @@ -497,6 +498,7 @@ "isRestParam": false } ], + "returnDescription": "A reference that can be used to unregister the callback registered by this call.", "rawComment": "/**\n * Register a callback to be invoked the next time the application finishes rendering, during the\n * `mixedReadWrite` phase.\n *\n *
\n *\n * You should prefer specifying an explicit phase for the callback instead, or you risk significant\n * performance degradation.\n *\n *
\n *\n * Note that the callback will run\n * - in the order it was registered\n * - on browser platforms only\n * - during the `mixedReadWrite` phase\n *\n *
\n *\n * Components are not guaranteed to be [hydrated](guide/hydration) before the callback runs.\n * You must use caution when directly reading or writing the DOM and layout.\n *\n *
\n *\n * @param callback A callback function to register\n * @param options Options to control the behavior of the callback\n *\n * @usageNotes\n *\n * Use `afterNextRender` to read or write the DOM once,\n * for example to initialize a non-Angular library.\n *\n * ### Example\n * ```angular-ts\n * @Component({\n * selector: 'my-chart-cmp',\n * template: `
{{ ... }}
`,\n * })\n * export class MyChartCmp {\n * @ViewChild('chart') chartRef: ElementRef;\n * chart: MyChart|null;\n *\n * constructor() {\n * afterNextRender({\n * write: () => {\n * this.chart = new MyChart(this.chartRef.nativeElement);\n * }\n * });\n * }\n * }\n * ```\n *\n * @publicApi 20.0\n */", "returnType": "AfterRenderRef" } diff --git a/adev/shared-docs/pipeline/api-gen/rendering/transforms/function-transforms.mts b/adev/shared-docs/pipeline/api-gen/rendering/transforms/function-transforms.mts index 46befb4cdbb..22ea35dc19d 100644 --- a/adev/shared-docs/pipeline/api-gen/rendering/transforms/function-transforms.mts +++ b/adev/shared-docs/pipeline/api-gen/rendering/transforms/function-transforms.mts @@ -20,7 +20,7 @@ import { setEntryFlags, } from './jsdoc-transforms.mjs'; import {addModuleName} from './module-name.mjs'; -import {addRenderableFunctionParams} from './params-transforms.mjs'; +import {addHtmlReturnDescription, addRenderableFunctionParams} from './params-transforms.mjs'; import {addRepo} from './repo.mjs'; /** Given an unprocessed function entry, get the fully renderable function entry. */ @@ -50,11 +50,13 @@ export function getFunctionMetadataRenderable( repo: string, ): FunctionSignatureMetadataRenderable { return addHtmlAdditionalLinks( - addRenderableFunctionParams( - addHtmlUsageNotes( - setEntryFlags( - addHtmlJsDocTagComments( - addHtmlDescription(addRepo(addModuleName(entry, moduleName), repo)), + addHtmlReturnDescription( + addRenderableFunctionParams( + addHtmlUsageNotes( + setEntryFlags( + addHtmlJsDocTagComments( + addHtmlDescription(addRepo(addModuleName(entry, moduleName), repo)), + ), ), ), ), diff --git a/adev/shared-docs/pipeline/api-gen/rendering/transforms/jsdoc-transforms.mts b/adev/shared-docs/pipeline/api-gen/rendering/transforms/jsdoc-transforms.mts index 5f067da7bb0..cee207415b9 100644 --- a/adev/shared-docs/pipeline/api-gen/rendering/transforms/jsdoc-transforms.mts +++ b/adev/shared-docs/pipeline/api-gen/rendering/transforms/jsdoc-transforms.mts @@ -112,7 +112,7 @@ export function addHtmlUsageNotes(entry: T): T & HasHtml } /** Given a markdown JsDoc text, gets the rendered HTML. */ -function getHtmlForJsDocText(text: string): string { +export function getHtmlForJsDocText(text: string): string { const mdToParse = convertLinks(wrapExampleHtmlElementsWithCode(text)); const parsed = parseMarkdown(mdToParse, { apiEntries: getSymbolsAsApiEntries(), diff --git a/adev/shared-docs/pipeline/api-gen/rendering/transforms/params-transforms.mts b/adev/shared-docs/pipeline/api-gen/rendering/transforms/params-transforms.mts index 3048b70cef0..2b374d2c852 100644 --- a/adev/shared-docs/pipeline/api-gen/rendering/transforms/params-transforms.mts +++ b/adev/shared-docs/pipeline/api-gen/rendering/transforms/params-transforms.mts @@ -7,7 +7,7 @@ */ import {HasModuleName, HasParams, HasRenderableParams} from '../entities/traits.mjs'; -import {addHtmlDescription} from './jsdoc-transforms.mjs'; +import {addHtmlDescription, getHtmlForJsDocText} from './jsdoc-transforms.mjs'; import {addModuleName} from './module-name.mjs'; export function addRenderableFunctionParams( @@ -22,3 +22,13 @@ export function addRenderableFunctionParams params, }; } + +/** Converts `returnDescription` to `htmlReturnDescription` for rendering. */ +export function addHtmlReturnDescription< + T extends {returnDescription?: string; moduleName: string}, +>(entry: T): T & {htmlReturnDescription?: string} { + const htmlReturnDescription = entry.returnDescription + ? getHtmlForJsDocText(entry.returnDescription) + : undefined; + return {...entry, htmlReturnDescription}; +} diff --git a/adev/shared-docs/styles/_reference.scss b/adev/shared-docs/styles/_reference.scss index fe251c4e079..39e3a87ac8d 100644 --- a/adev/shared-docs/styles/_reference.scss +++ b/adev/shared-docs/styles/_reference.scss @@ -340,6 +340,11 @@ } } + .docs-param-type { + display: inline-block; + margin-inline-end: 0.5rem; + } + .docs-parameter-description { p:first-child { margin-block-start: 0; @@ -357,7 +362,7 @@ padding-block: 1rem; // & does not follow a function definition - &:not(.docs-function-definition + .docs-return-type) { + &:not(.docs-function-definition + .docs-return-type):not(:first-child) { border-block-start: 1px solid var(--senary-contrast); } } diff --git a/packages/compiler-cli/src/ngtsc/docs/src/function_extractor.ts b/packages/compiler-cli/src/ngtsc/docs/src/function_extractor.ts index 7038b540245..3a67366e5f0 100644 --- a/packages/compiler-cli/src/ngtsc/docs/src/function_extractor.ts +++ b/packages/compiler-cli/src/ngtsc/docs/src/function_extractor.ts @@ -162,17 +162,21 @@ function filterSignatureDeclarations(signatures: readonly ts.Signature[]) { } export function extractCallSignatures(name: string, typeChecker: ts.TypeChecker, type: ts.Type) { - return filterSignatureDeclarations(type.getCallSignatures()).map(({decl, signature}) => ({ - name, - entryType: EntryType.Function, - description: extractJsDocDescription(decl), - generics: extractGenerics(decl), - isNewType: false, - jsdocTags: extractJsDocTags(decl), - params: extractAllParams(decl.parameters, typeChecker), - rawComment: extractRawJsDoc(decl), - returnType: extractReturnType(signature, typeChecker), - })); + return filterSignatureDeclarations(type.getCallSignatures()).map(({decl, signature}) => { + const jsdocTags = extractJsDocTags(decl); + return { + name, + entryType: EntryType.Function, + description: extractJsDocDescription(decl), + generics: extractGenerics(decl), + isNewType: false, + jsdocTags, + params: extractAllParams(decl.parameters, typeChecker), + rawComment: extractRawJsDoc(decl), + returnType: extractReturnType(signature, typeChecker), + returnDescription: jsdocTags.find((tag) => tag.name === 'returns')?.comment, + }; + }); } function extractReturnType(signature: ts.Signature, typeChecker: ts.TypeChecker): string {