docs(docs-infra): Extract class constructors (#60302)

fixes #59180

PR Close #60302
This commit is contained in:
Matthieu Riegler 2025-03-10 12:07:25 +01:00 committed by Andrew Kushnir
parent 647550918f
commit db5963797d
4 changed files with 658 additions and 15 deletions

View file

@ -754,6 +754,525 @@
"startLine": 112,
"endLine": 115
}
},
{
"name": "HttpRequest",
"isAbstract": false,
"entryType": "undecorated_class",
"members": [
{
"name": "constructor",
"signatures": [
{
"name": "constructor",
"params": [
{
"name": "method",
"description": "",
"type": "\"GET\" | \"HEAD\"",
"isOptional": false,
"isRestParam": false
},
{
"name": "url",
"description": "",
"type": "string",
"isOptional": false,
"isRestParam": false
},
{
"name": "init",
"description": "",
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; } | undefined",
"isOptional": true,
"isRestParam": false
}
],
"returnType": "foobar",
"description": "",
"entryType": "function",
"jsdocTags": [],
"rawComment": "",
"generics": [],
"isNewType": false
},
{
"name": "constructor",
"params": [
{
"name": "method",
"description": "",
"type": "\"DELETE\" | \"JSONP\" | \"OPTIONS\"",
"isOptional": false,
"isRestParam": false
},
{
"name": "url",
"description": "",
"type": "string",
"isOptional": false,
"isRestParam": false
},
{
"name": "init",
"description": "",
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; } | undefined",
"isOptional": true,
"isRestParam": false
}
],
"returnType": "foobar",
"description": "",
"entryType": "function",
"jsdocTags": [],
"rawComment": "",
"generics": [],
"isNewType": false
},
{
"name": "constructor",
"params": [
{
"name": "method",
"description": "",
"type": "\"POST\"",
"isOptional": false,
"isRestParam": false
},
{
"name": "url",
"description": "",
"type": "string",
"isOptional": false,
"isRestParam": false
},
{
"name": "body",
"description": "",
"type": "T | null",
"isOptional": false,
"isRestParam": false
},
{
"name": "init",
"description": "",
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; } | undefined",
"isOptional": true,
"isRestParam": false
}
],
"returnType": "foobar",
"description": "",
"entryType": "function",
"jsdocTags": [],
"rawComment": "",
"generics": [],
"isNewType": false
},
{
"name": "constructor",
"params": [
{
"name": "method",
"description": "",
"type": "\"PUT\" | \"PATCH\"",
"isOptional": false,
"isRestParam": false
},
{
"name": "url",
"description": "",
"type": "string",
"isOptional": false,
"isRestParam": false
},
{
"name": "body",
"description": "",
"type": "T | null",
"isOptional": false,
"isRestParam": false
},
{
"name": "init",
"description": "",
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; } | undefined",
"isOptional": true,
"isRestParam": false
}
],
"returnType": "foobar",
"description": "",
"entryType": "function",
"jsdocTags": [],
"rawComment": "",
"generics": [],
"isNewType": false
},
{
"name": "constructor",
"params": [
{
"name": "method",
"description": "",
"type": "string",
"isOptional": false,
"isRestParam": false
},
{
"name": "url",
"description": "",
"type": "string",
"isOptional": false,
"isRestParam": false
},
{
"name": "body",
"description": "",
"type": "T | null",
"isOptional": false,
"isRestParam": false
},
{
"name": "init",
"description": "",
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; } | undefined",
"isOptional": true,
"isRestParam": false
}
],
"returnType": "foobar",
"description": "",
"entryType": "function",
"jsdocTags": [],
"rawComment": "",
"generics": [],
"isNewType": false
},
{
"name": "constructor",
"params": [
{
"name": "method",
"description": "",
"type": "string",
"isOptional": false,
"isRestParam": false
},
{
"name": "url",
"description": "",
"type": "string",
"isOptional": false,
"isRestParam": false
},
{
"name": "third",
"description": "",
"type": "T | { headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; } | null | undefined",
"isOptional": true,
"isRestParam": false
},
{
"name": "fourth",
"description": "",
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; } | undefined",
"isOptional": true,
"isRestParam": false
}
],
"returnType": "foobar",
"description": "",
"entryType": "function",
"jsdocTags": [],
"rawComment": "",
"generics": [],
"isNewType": false
}
],
"implementation": {
"params": [
{
"name": "method",
"description": "",
"type": "string",
"isOptional": false,
"isRestParam": false
},
{
"name": "url",
"description": "",
"type": "string",
"isOptional": false,
"isRestParam": false
},
{
"name": "third",
"description": "",
"type": "T | { headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; } | null | undefined",
"isOptional": true,
"isRestParam": false
},
{
"name": "fourth",
"description": "",
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; } | undefined",
"isOptional": true,
"isRestParam": false
}
],
"isNewType": false,
"returnType": "HttpRequest<T>",
"generics": [],
"name": "constructor",
"description": "",
"entryType": "function",
"jsdocTags": [],
"rawComment": ""
},
"entryType": "function",
"description": "",
"jsdocTags": [],
"rawComment": "",
"memberType": "method",
"memberTags": []
},
{
"name": "body",
"type": "T | null",
"memberType": "property",
"memberTags": ["readonly"],
"description": "The request body, or `null` if one isn't set.\n\nBodies are not enforced to be immutable, as they can include a reference to any\nuser-defined data type. However, interceptors should take care to preserve\nidempotence by treating them as such.",
"jsdocTags": []
},
{
"name": "headers",
"type": "HttpHeaders",
"memberType": "property",
"memberTags": ["readonly"],
"description": "Outgoing headers for this request.",
"jsdocTags": []
},
{
"name": "context",
"type": "HttpContext",
"memberType": "property",
"memberTags": ["readonly"],
"description": "Shared and mutable context that can be used by interceptors",
"jsdocTags": []
},
{
"name": "reportProgress",
"type": "boolean",
"memberType": "property",
"memberTags": ["readonly"],
"description": "Whether this request should be made in a way that exposes progress events.\n\nProgress events are expensive (change detection runs on each event) and so\nthey should only be requested if the consumer intends to monitor them.\n\nNote: The `FetchBackend` doesn't support progress report on uploads.",
"jsdocTags": []
},
{
"name": "withCredentials",
"type": "boolean",
"memberType": "property",
"memberTags": ["readonly"],
"description": "Whether this request should be sent with outgoing credentials (cookies).",
"jsdocTags": []
},
{
"name": "responseType",
"type": "\"arraybuffer\" | \"blob\" | \"text\" | \"json\"",
"memberType": "property",
"memberTags": ["readonly"],
"description": "The expected response type of the server.\n\nThis is used to parse the response appropriately before returning it to\nthe requestee.",
"jsdocTags": []
},
{
"name": "method",
"type": "string",
"memberType": "property",
"memberTags": ["readonly"],
"description": "The outgoing HTTP request method.",
"jsdocTags": []
},
{
"name": "params",
"type": "HttpParams",
"memberType": "property",
"memberTags": ["readonly"],
"description": "Outgoing URL parameters.\n\nTo pass a string representation of HTTP parameters in the URL-query-string format,\nthe `HttpParamsOptions`' `fromString` may be used. For example:\n\n```ts\nnew HttpParams({fromString: 'angular=awesome'})\n```",
"jsdocTags": []
},
{
"name": "urlWithParams",
"type": "string",
"memberType": "property",
"memberTags": ["readonly"],
"description": "The outgoing URL with all URL parameters set.",
"jsdocTags": []
},
{
"name": "transferCache",
"type": "boolean | { includeHeaders?: string[] | undefined; } | undefined",
"memberType": "property",
"memberTags": ["readonly", "optional"],
"description": "The HttpTransferCache option for the request",
"jsdocTags": []
},
{
"name": "serializeBody",
"signatures": [
{
"name": "serializeBody",
"entryType": "function",
"description": "Transform the free-form body into a serialized format suitable for\ntransmission to the server.",
"generics": [],
"isNewType": false,
"jsdocTags": [],
"params": [],
"rawComment": "/**\n * Transform the free-form body into a serialized format suitable for\n * transmission to the server.\n */",
"returnType": "string | ArrayBuffer | Blob | FormData | URLSearchParams | null"
}
],
"implementation": {
"params": [],
"isNewType": false,
"returnType": "string | ArrayBuffer | Blob | FormData | URLSearchParams | null",
"generics": [],
"name": "serializeBody",
"description": "Transform the free-form body into a serialized format suitable for\ntransmission to the server.",
"entryType": "function",
"jsdocTags": [],
"rawComment": "/**\n * Transform the free-form body into a serialized format suitable for\n * transmission to the server.\n */"
},
"entryType": "function",
"description": "Transform the free-form body into a serialized format suitable for\ntransmission to the server.",
"jsdocTags": [],
"rawComment": "/**\n * Transform the free-form body into a serialized format suitable for\n * transmission to the server.\n */",
"memberType": "method",
"memberTags": []
},
{
"name": "detectContentTypeHeader",
"signatures": [
{
"name": "detectContentTypeHeader",
"entryType": "function",
"description": "Examine the body and attempt to infer an appropriate MIME type\nfor it.\n\nIf no such type can be inferred, this method will return `null`.",
"generics": [],
"isNewType": false,
"jsdocTags": [],
"params": [],
"rawComment": "/**\n * Examine the body and attempt to infer an appropriate MIME type\n * for it.\n *\n * If no such type can be inferred, this method will return `null`.\n */",
"returnType": "string | null"
}
],
"implementation": {
"params": [],
"isNewType": false,
"returnType": "string | null",
"generics": [],
"name": "detectContentTypeHeader",
"description": "Examine the body and attempt to infer an appropriate MIME type\nfor it.\n\nIf no such type can be inferred, this method will return `null`.",
"entryType": "function",
"jsdocTags": [],
"rawComment": "/**\n * Examine the body and attempt to infer an appropriate MIME type\n * for it.\n *\n * If no such type can be inferred, this method will return `null`.\n */"
},
"entryType": "function",
"description": "Examine the body and attempt to infer an appropriate MIME type\nfor it.\n\nIf no such type can be inferred, this method will return `null`.",
"jsdocTags": [],
"rawComment": "/**\n * Examine the body and attempt to infer an appropriate MIME type\n * for it.\n *\n * If no such type can be inferred, this method will return `null`.\n */",
"memberType": "method",
"memberTags": []
},
{
"name": "clone",
"signatures": [
{
"name": "clone",
"entryType": "function",
"description": "",
"generics": [],
"isNewType": false,
"jsdocTags": [],
"params": [],
"rawComment": "",
"returnType": "HttpRequest<T>"
},
{
"name": "clone",
"entryType": "function",
"description": "",
"generics": [],
"isNewType": false,
"jsdocTags": [],
"params": [
{
"name": "update",
"description": "",
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; body?: T | null | undefined; method?: string | undefined; url?: string | undefined; setHeaders?: { [name: string]: string | string[]; } | undefined; setParams?: { [param: string]: string; } | undefined; }",
"isOptional": false,
"isRestParam": false
}
],
"rawComment": "",
"returnType": "HttpRequest<T>"
},
{
"name": "clone",
"entryType": "function",
"description": "",
"generics": [{"name": "V"}],
"isNewType": false,
"jsdocTags": [],
"params": [
{
"name": "update",
"description": "",
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; body?: V | null | undefined; method?: string | undefined; url?: string | undefined; setHeaders?: { [name: string]: string | string[]; } | undefined; setParams?: { [param: string]: string; } | undefined; }",
"isOptional": false,
"isRestParam": false
}
],
"rawComment": "",
"returnType": "HttpRequest<V>"
}
],
"implementation": {
"params": [
{
"name": "update",
"description": "",
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; body?: any; method?: string | undefined; url?: string | undefined; setHeaders?: { [name: string]: string | string[]; } | undefined; setParams?: { [param: string]: string; } | undefined; }",
"isOptional": true,
"isRestParam": false
}
],
"isNewType": false,
"returnType": "HttpRequest<any>",
"generics": [],
"name": "clone",
"description": "",
"entryType": "function",
"jsdocTags": [],
"rawComment": ""
},
"entryType": "function",
"description": "",
"jsdocTags": [],
"rawComment": "",
"memberType": "method",
"memberTags": []
}
],
"generics": [{"name": "T"}],
"description": "An outgoing HTTP request with an optional typed body.\n\n`HttpRequest` represents an outgoing request, including URL, method,\nheaders, body, and other request configuration options. Instances should be\nassumed to be immutable. To modify a `HttpRequest`, the `clone`\nmethod should be used.",
"jsdocTags": [{"name": "publicApi", "comment": ""}],
"rawComment": "/**\n * An outgoing HTTP request with an optional typed body.\n *\n * `HttpRequest` represents an outgoing request, including URL, method,\n * headers, body, and other request configuration options. Instances should be\n * assumed to be immutable. To modify a `HttpRequest`, the `clone`\n * method should be used.\n *\n * @publicApi\n */",
"implements": [],
"source": {
"filePath": "/packages/common/http/src/request.ts",
"startLine": 131,
"endLine": 578
}
}
],
"symbols": [

View file

@ -117,6 +117,8 @@ class ClassExtractor {
return this.extractClassProperty(memberDeclaration);
} else if (ts.isAccessor(memberDeclaration)) {
return this.extractGetterSetter(memberDeclaration);
} else if (ts.isConstructorDeclaration(memberDeclaration)) {
return this.extractConstructor(memberDeclaration);
}
// We only expect methods, properties, and accessors. If we encounter something else,
@ -180,6 +182,19 @@ class ClassExtractor {
};
}
protected extractConstructor(constructorDeclaration: ts.ConstructorDeclaration): MethodEntry {
const functionExtractor = new FunctionExtractor(
'constructor',
constructorDeclaration,
this.typeChecker,
);
return {
...functionExtractor.extract(),
memberType: MemberType.Method,
memberTags: this.getMemberTags(constructorDeclaration),
};
}
protected extractInheritance(
declaration: ClassDeclaration & ClassDeclarationLike,
): string | undefined {
@ -211,7 +226,9 @@ class ClassExtractor {
}
/** Gets the tags for a member (protected, readonly, static, etc.) */
protected getMemberTags(member: MethodLike | PropertyLike): MemberTags[] {
protected getMemberTags(
member: MethodLike | PropertyLike | ts.ConstructorDeclaration,
): MemberTags[] {
const tags: MemberTags[] = this.getMemberTagsFromModifiers(member.modifiers ?? []);
if (member.questionToken) {
@ -248,6 +265,7 @@ class ClassExtractor {
// classes may narrow types or add method overloads.
const type = this.typeChecker.getTypeAtLocation(this.declaration);
const members = type.getProperties();
const constructor = type.getSymbol()?.members?.get(ts.InternalSymbolName.Constructor);
// While the properties of the declaration type represent the properties that exist
// on a class *instance*, static members are properties on the class symbol itself.
@ -255,7 +273,7 @@ class ClassExtractor {
const staticMembers = typeOfConstructor.getProperties();
const result: MemberElement[] = [];
for (const member of [...members, ...staticMembers]) {
for (const member of [...(constructor ? [constructor] : []), ...members, ...staticMembers]) {
// A member may have multiple declarations in the case of function overloads.
const memberDeclarations = this.filterMethodOverloads(member.getDeclarations() ?? []);
for (const memberDeclaration of memberDeclarations) {
@ -272,17 +290,23 @@ class ClassExtractor {
private filterMethodOverloads(declarations: ts.Declaration[]): ts.Declaration[] {
return declarations.filter((declaration, index) => {
// Check if the declaration is a function or method
if (ts.isFunctionDeclaration(declaration) || ts.isMethodDeclaration(declaration)) {
if (
ts.isFunctionDeclaration(declaration) ||
ts.isMethodDeclaration(declaration) ||
ts.isConstructorDeclaration(declaration)
) {
// TypeScript ensures that all declarations for a given abstract method appear consecutively.
const nextDeclaration = declarations[index + 1];
const isNextAbstractMethodWithSameName =
const isNextMethodWithSameName =
nextDeclaration &&
ts.isMethodDeclaration(nextDeclaration) &&
nextDeclaration.name.getText() === declaration.name?.getText();
((ts.isMethodDeclaration(nextDeclaration) &&
nextDeclaration.name.getText() === declaration.name?.getText()) ||
(ts.isConstructorDeclaration(nextDeclaration) &&
ts.isConstructorDeclaration(declaration)));
// Return only the last occurrence of an abstract method to avoid overload duplication.
// Return only the last occurrence of a method to avoid overload duplication.
// Subsequent overloads or implementations are handled separately by the function extractor.
return !isNextAbstractMethodWithSameName;
return !isNextMethodWithSameName;
}
// Include non-method declarations, such as properties, without filtering.
@ -326,6 +350,11 @@ class ClassExtractor {
* - The member is marked as internal via JSDoc.
*/
private isMemberExcluded(member: MemberElement): boolean {
if (ts.isConstructorDeclaration(member)) {
// A constructor has no name
return false;
}
return (
!member.name ||
!this.isDocumentableMember(member) ||
@ -345,6 +374,7 @@ class ClassExtractor {
this.isMethod(member) ||
this.isProperty(member) ||
ts.isAccessor(member) ||
ts.isConstructorDeclaration(member) ||
// Signatures are documentable if they are part of an interface.
ts.isCallSignatureDeclaration(member)
);

View file

@ -8,7 +8,7 @@
import ts from 'typescript';
import {EntryType, FunctionEntry, ParameterEntry} from './entities';
import {EntryType, FunctionEntry, FunctionSignatureMetadata, ParameterEntry} from './entities';
import {extractGenerics} from './generics_extractor';
import {extractJsDocDescription, extractJsDocTags, extractRawJsDoc} from './jsdoc_extractor';
import {extractResolvedTypeString} from './type_extractor';
@ -18,7 +18,8 @@ export type FunctionLike =
| ts.MethodDeclaration
| ts.MethodSignature
| ts.CallSignatureDeclaration
| ts.ConstructSignatureDeclaration;
| ts.ConstructSignatureDeclaration
| ts.ConstructorDeclaration;
export class FunctionExtractor {
constructor(
@ -45,7 +46,9 @@ export class FunctionExtractor {
this.exportDeclaration;
const type = this.typeChecker.getTypeAtLocation(this.exportDeclaration);
const overloads = extractCallSignatures(this.name, this.typeChecker, type);
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);
@ -72,6 +75,32 @@ export class FunctionExtractor {
}
}
function constructorOverloads(
constructorDeclaration: ts.ConstructorDeclaration,
typeChecker: ts.TypeChecker,
) {
const classDeclaration = constructorDeclaration.parent;
const constructorNode = classDeclaration.members.filter(
(member): member is ts.ConstructorDeclaration => {
return ts.isConstructorDeclaration(member) && !member.body;
},
);
return constructorNode.map((n): FunctionSignatureMetadata => {
return {
name: 'constructor',
params: extractAllParams(n.parameters, typeChecker),
returnType: typeChecker.getTypeAtLocation(classDeclaration)?.symbol.name,
description: extractJsDocDescription(n),
entryType: EntryType.Function,
jsdocTags: extractJsDocTags(n),
rawComment: extractRawJsDoc(n),
generics: extractGenerics(n),
isNewType: false,
};
});
}
/** Extracts parameters of the given parameter declaration AST nodes. */
export function extractAllParams(
params: ts.NodeArray<ts.ParameterDeclaration>,
@ -90,14 +119,19 @@ export function extractAllParams(
function filterSignatureDeclarations(signatures: readonly ts.Signature[]) {
const result: Array<{
signature: ts.Signature;
decl: ts.FunctionDeclaration | ts.CallSignatureDeclaration | ts.MethodDeclaration;
decl:
| ts.FunctionDeclaration
| ts.CallSignatureDeclaration
| ts.MethodDeclaration
| ts.ConstructSignatureDeclaration;
}> = [];
for (const signature of signatures) {
const decl = signature.getDeclaration();
if (
ts.isFunctionDeclaration(decl) ||
ts.isCallSignatureDeclaration(decl) ||
ts.isMethodDeclaration(decl)
ts.isMethodDeclaration(decl) ||
ts.isConstructSignatureDeclaration(decl)
) {
result.push({signature, decl});
}

View file

@ -55,6 +55,61 @@ runInEachFileSystem(() => {
expect(customSliderEntry.entryType).toBe(EntryType.UndecoratedClass);
});
it('should extract class constructor', () => {
env.write(
'index.ts',
`
export class UserProfile {
constructor(foo: number) {}
}
`,
);
const docs: DocEntry[] = env.driveDocsExtraction('index.ts');
const classEntry = docs[0] as ClassEntry;
expect(classEntry.members.length).toBe(1);
const methodEntry = classEntry.members[0] as MethodEntry;
expect(methodEntry.memberType).toBe(MemberType.Method);
expect(methodEntry.name).toBe('constructor');
expect(methodEntry.implementation.params.length).toBe(1);
expect(methodEntry.implementation.params[0].name).toBe('foo');
expect(methodEntry.implementation.params[0].type).toBe('number');
});
it('should extract class constructor with overloads', () => {
env.write(
'index.ts',
`
export class UserProfile {
constructor(value: boolean);
constructor(value: number);
constructor(value: number | boolean | string) { }
}
`,
);
const docs: DocEntry[] = env.driveDocsExtraction('index.ts');
const classEntry = docs[0] as ClassEntry;
expect(classEntry.members.length).toBe(1);
const constructorEntry = classEntry.members[0] as MethodEntry;
expect(constructorEntry.signatures.length).toBe(2);
const [booleanOverloadEntry, numberOverloadEntry] = constructorEntry.signatures;
expect(booleanOverloadEntry.name).toBe('constructor');
expect(booleanOverloadEntry.params.length).toBe(1);
expect(booleanOverloadEntry.params[0].type).toBe('boolean');
expect(booleanOverloadEntry.returnType).toBe('UserProfile');
expect(numberOverloadEntry.name).toBe('constructor');
expect(numberOverloadEntry.params.length).toBe(1);
expect(numberOverloadEntry.params[0].type).toBe('number');
expect(numberOverloadEntry.returnType).toBe('UserProfile');
});
it('should extract class members', () => {
env.write(
'index.ts',
@ -650,9 +705,14 @@ runInEachFileSystem(() => {
expect(docs.length).toBe(1);
const classEntry = docs[0] as ClassEntry;
expect(classEntry.members.length).toBe(2);
const [myPropEntry, fooEntry] = classEntry.members as PropertyEntry[];
// constructor, myProp, foo (others are non-public)
expect(classEntry.members.length).toBe(3);
const [constructorEntry, myPropEntry, fooEntry] = classEntry.members as PropertyEntry[];
expect(constructorEntry.name).toBe('constructor');
expect(constructorEntry.memberType).toBe(MemberType.Method);
expect(myPropEntry.name).toBe('myProp');
expect(myPropEntry.memberType).toBe(MemberType.Property);