@jsdocs-io/extractor
Version:
The API extractor for npm packages powering jsdocs.io
149 lines (148 loc) • 6.09 kB
JavaScript
import { orderBy } from "natural-orderby";
import { Node, } from "ts-morph";
import { apparentType } from "./apparent-type.js";
import { docs } from "./docs.js";
import { formatSignature } from "./format-signature.js";
import { headText } from "./head-text.js";
import { id } from "./id.js";
import { isHidden } from "./is-hidden.js";
import { modifiersText } from "./modifiers-text.js";
import { sourceFilePath } from "./source-file-path.js";
import { typeCheckerType } from "./type-checker-type.js";
export async function extractClass(containerName, exportName, declaration) {
const classId = id(containerName, "+class", exportName);
return {
kind: "class",
id: classId,
name: exportName,
docs: docs(declaration),
file: sourceFilePath(declaration),
line: declaration.getStartLineNumber(),
signature: await classSignature(declaration),
constructors: await extractClassConstructors(classId, declaration),
properties: await extractClassProperties(classId, declaration),
methods: await extractClassMethods(classId, declaration),
};
}
async function classSignature(declaration) {
const signature = headText(declaration);
return await formatSignature("class", signature);
}
async function extractClassConstructors(classId, classDeclaration) {
// Calling `getConstructors()` returns all constructors in ambient modules
// but only the implementation constructor in normal modules.
// We get the first constructor and then find all others starting from it.
const declaration = classDeclaration.getConstructors().at(0);
if (!declaration)
return [];
const implementation = declaration.getImplementation();
const constructorDeclarations = [
...(implementation ? [implementation] : []),
...declaration.getOverloads(),
];
const constructors = [];
for (const [index, declaration] of constructorDeclarations.entries()) {
if (isHidden(declaration))
continue;
constructors.push({
kind: "class-constructor",
id: id(classId, "constructor", index > 0 ? `${index}` : ""),
name: "constructor",
docs: docs(declaration),
file: sourceFilePath(declaration),
line: declaration.getStartLineNumber(),
signature: await classConstructorSignature(declaration),
});
}
return constructors;
}
async function classConstructorSignature(declaration) {
const modifiers = modifiersText(declaration);
const params = declaration
.getParameters()
.map((param) => {
const name = param.getName();
const type = apparentType(param);
const isRest = param.isRestParameter();
const dotsToken = isRest ? "..." : "";
const isOptional = param.isOptional();
const questionToken = !isRest && isOptional ? "?" : "";
return `${dotsToken}${name}${questionToken}: ${type}`;
})
.join(",");
const signature = `${modifiers} constructor(${params});`;
return await formatSignature("class-constructor", signature);
}
async function extractClassProperties(classId, classDeclaration) {
const propertiesDeclarations = [
...classDeclaration.getInstanceProperties(),
...classDeclaration.getStaticProperties(),
];
const properties = [];
for (const declaration of propertiesDeclarations) {
if (!(Node.isParameterDeclaration(declaration) ||
Node.isPropertyDeclaration(declaration) ||
Node.isGetAccessorDeclaration(declaration))) {
continue;
}
if (isHidden(declaration))
continue;
const name = declaration.getName();
properties.push({
kind: "class-property",
id: id(classId, "+property", name),
name,
docs: docs(declaration),
file: sourceFilePath(declaration),
line: declaration.getStartLineNumber(),
signature: await classPropertySignature(name, declaration),
});
}
return orderBy(properties, "id");
}
async function classPropertySignature(name, declaration) {
const type = apparentType(declaration);
if (Node.isParameterDeclaration(declaration) || Node.isPropertyDeclaration(declaration)) {
const modifiers = modifiersText(declaration);
const optional = declaration.hasQuestionToken() ? "?" : "";
const signature = `${modifiers} ${name}${optional}: ${type}`;
return formatSignature("class-property", signature);
}
// GetAccessorDeclaration.
const staticKeyword = declaration.isStatic() ? "static" : "";
const readonlyKeyword = declaration.getSetAccessor() === undefined ? "readonly" : "";
const signature = `${staticKeyword} ${readonlyKeyword} ${name}: ${type}`;
return await formatSignature("class-property", signature);
}
async function extractClassMethods(classId, classDeclaration) {
const methodsDeclarations = [
...classDeclaration.getInstanceMethods(),
...classDeclaration.getStaticMethods(),
];
const methods = [];
const seenMethods = new Set();
for (const declaration of methodsDeclarations) {
const name = declaration.getName();
// Skip overloaded methods.
if (seenMethods.has(name))
continue;
if (isHidden(declaration))
continue;
seenMethods.add(name);
methods.push({
kind: "class-method",
id: id(classId, "+method", name),
name,
docs: docs(declaration),
file: sourceFilePath(declaration),
line: declaration.getStartLineNumber(),
signature: await classMethodSignature(name, declaration),
});
}
return orderBy(methods, "id");
}
async function classMethodSignature(name, declaration) {
const modifiers = modifiersText(declaration);
const type = typeCheckerType(declaration);
return await formatSignature("class-method", `${modifiers} ${name}: ${type}`);
}