docs(docs-infra): improve api docs extraction

This allows us to show the API docs when the jsdoc block is at the top of a overloaded function (and not on the implementation signature).

eg: `injectAsync`
(cherry picked from commit 7360c1da68)
This commit is contained in:
Matthieu Riegler 2026-05-01 13:05:23 +02:00 committed by Matthew Beck
parent 0601dc579a
commit 78ec99d146
2 changed files with 88 additions and 9 deletions

View file

@ -42,8 +42,18 @@ export class FunctionExtractor {
const overloads = ts.isConstructorDeclaration(this.exportDeclaration)
? constructorOverloads(this.exportDeclaration, this.typeChecker)
: extractCallSignatures(this.name, this.typeChecker, type);
const jsdocsTags = extractJsDocTags(implementation);
const description = extractJsDocDescription(implementation);
const implementationJsDocTags = extractJsDocTags(implementation);
const implementationDescription = extractJsDocDescription(implementation);
const implementationRawComment = extractRawJsDoc(implementation);
const docsForFunctionEntry = extractFunctionEntryDocs(overloads, {
description: implementationDescription,
jsdocTags: implementationJsDocTags,
rawComment: implementationRawComment,
}) ?? {
description: implementationDescription,
jsdocTags: implementationJsDocTags,
rawComment: implementationRawComment,
};
return {
name: this.name,
@ -52,22 +62,41 @@ export class FunctionExtractor {
params: extractAllParams(implementation.parameters, this.typeChecker),
isNewType: ts.isConstructSignatureDeclaration(implementation),
returnType,
returnDescription: jsdocsTags.find((tag) => tag.name === 'returns')?.comment,
returnDescription: implementationJsDocTags.find((tag) => tag.name === 'returns')?.comment,
generics: extractGenerics(implementation),
name: this.name,
description,
description: implementationDescription,
entryType: EntryType.Function,
jsdocTags: jsdocsTags,
rawComment: extractRawJsDoc(implementation),
jsdocTags: implementationJsDocTags,
rawComment: implementationRawComment,
},
entryType: EntryType.Function,
description,
jsdocTags: jsdocsTags,
rawComment: extractRawJsDoc(implementation),
description: docsForFunctionEntry.description,
jsdocTags: docsForFunctionEntry.jsdocTags,
rawComment: docsForFunctionEntry.rawComment,
};
}
}
function extractFunctionEntryDocs(
overloads: FunctionSignatureMetadata[],
implementationDocs: Pick<FunctionSignatureMetadata, 'description' | 'jsdocTags' | 'rawComment'>,
): Pick<FunctionSignatureMetadata, 'description' | 'jsdocTags' | 'rawComment'> | null {
if (hasJSDocContent(implementationDocs)) {
return implementationDocs;
}
return overloads.find((overload) => hasJSDocContent(overload)) ?? null;
}
function hasJSDocContent(
docs: Pick<FunctionSignatureMetadata, 'description' | 'jsdocTags' | 'rawComment'>,
): boolean {
return (
docs.description.trim() !== '' || docs.rawComment.trim() !== '' || docs.jsdocTags.length > 0
);
}
function constructorOverloads(
constructorDeclaration: ts.ConstructorDeclaration,
typeChecker: ts.TypeChecker,

View file

@ -126,6 +126,56 @@ runInEachFileSystem(() => {
expect(numberOverloadEntry.returnType).toBe('number');
});
it('should use overload docs when implementation has no docs', () => {
env.write(
'index.ts',
`
/**
* Overload docs.
*
* @publicApi
*/
export function ident(value: boolean): boolean
export function ident(value: boolean|number): boolean|number {
return value;
}
`,
);
const docs = env.driveDocsExtraction('index.ts') as FunctionEntry[];
const [functionEntry] = docs;
expect(functionEntry.description).toContain('Overload docs.');
expect(functionEntry.rawComment).toContain('Overload docs.');
expect(functionEntry.jsdocTags.some((tag) => tag.name === 'publicApi')).toBeTrue();
expect(functionEntry.implementation.description).toBe('');
});
it('should prefer implementation docs over overload docs', () => {
env.write(
'index.ts',
`
/** Overload docs. */
export function ident(value: boolean): boolean
/**
* Implementation docs.
*
* @publicApi
*/
export function ident(value: boolean|number): boolean|number {
return value;
}
`,
);
const docs = env.driveDocsExtraction('index.ts') as FunctionEntry[];
const [functionEntry] = docs;
expect(functionEntry.description).toContain('Implementation docs.');
expect(functionEntry.rawComment).toContain('Implementation docs.');
expect(functionEntry.jsdocTags.some((tag) => tag.name === 'publicApi')).toBeTrue();
});
it('should extract function generics', () => {
env.write(
'index.ts',