typedoc
Version:
Create api documentation for TypeScript projects.
341 lines (340 loc) • 15.8 kB
JavaScript
import ts from "typescript";
import assert from "assert";
import { DeclarationReflection, IntrinsicType, ParameterReflection, PredicateType, ReferenceType, ReflectionFlag, ReflectionKind, SignatureReflection, TypeParameterReflection, VarianceModifier, } from "../../models/index.js";
import { ConverterEvents } from "../converter-events.js";
import { convertDefaultValue } from "../convert-expression.js";
import { removeUndefined } from "../utils/reflections.js";
import { createSymbolId } from "./symbol-id.js";
export function createSignature(context, kind, signature, symbol, declaration) {
assert(context.scope instanceof DeclarationReflection);
declaration ||= signature.getDeclaration();
const sigRef = new SignatureReflection(kind == ReflectionKind.ConstructorSignature
? context.scope.parent.name
: context.scope.name, kind, context.scope);
// This feels awful, but we need some way to tell if callable signatures on classes
// are "static" (e.g. `Foo()`) or not (e.g. `(new Foo())()`)
if (context.shouldBeStatic) {
sigRef.setFlag(ReflectionFlag.Static);
}
if (symbol && declaration) {
context.project.registerSymbolId(sigRef, createSymbolId(symbol, declaration));
}
let parentReflection = context.scope;
if (parentReflection.kindOf(ReflectionKind.TypeLiteral) &&
parentReflection.parent instanceof DeclarationReflection) {
parentReflection = parentReflection.parent;
}
if (declaration) {
const sigComment = context.getSignatureComment(declaration);
if (parentReflection.comment?.discoveryId !== sigComment?.discoveryId) {
sigRef.comment = sigComment;
if (parentReflection.kindOf(ReflectionKind.MayContainDocuments)) {
context.converter.processDocumentTags(sigRef, parentReflection);
}
}
}
const sigRefCtx = context.withScope(sigRef);
sigRef.typeParameters = convertTypeParameters(sigRefCtx, sigRef, signature.typeParameters);
const parameterSymbols = signature.thisParameter
? [signature.thisParameter, ...signature.parameters]
: signature.parameters;
sigRef.parameters = convertParameters(sigRefCtx, sigRef, parameterSymbols, declaration?.parameters);
const predicate = context.checker.getTypePredicateOfSignature(signature);
if (predicate) {
sigRef.type = convertPredicate(predicate, sigRefCtx);
}
else if (kind == ReflectionKind.SetSignature) {
sigRef.type = new IntrinsicType("void");
}
else if (declaration?.type?.kind === ts.SyntaxKind.ThisType) {
sigRef.type = new IntrinsicType("this");
}
else {
let typeNode = declaration?.type;
if (typeNode && ts.isJSDocReturnTag(typeNode)) {
typeNode = typeNode.typeExpression?.type;
}
sigRef.type = context.converter.convertType(sigRefCtx, signature.getReturnType(), typeNode);
}
context.registerReflection(sigRef, undefined);
switch (kind) {
case ReflectionKind.GetSignature:
context.scope.getSignature = sigRef;
break;
case ReflectionKind.SetSignature:
context.scope.setSignature = sigRef;
break;
case ReflectionKind.CallSignature:
case ReflectionKind.ConstructorSignature:
context.scope.signatures ??= [];
context.scope.signatures.push(sigRef);
break;
}
context.converter.trigger(ConverterEvents.CREATE_SIGNATURE, context, sigRef, declaration, signature);
}
/**
* Special cased constructor factory for functions tagged with `@class`
*/
export function createConstructSignatureWithType(context, signature, classType) {
assert(context.scope instanceof DeclarationReflection);
const declaration = signature.getDeclaration();
const sigRef = new SignatureReflection(context.scope.parent.name, ReflectionKind.ConstructorSignature, context.scope);
const sigRefCtx = context.withScope(sigRef);
if (declaration) {
sigRef.comment = context.getSignatureComment(declaration);
if (sigRef.comment?.discoveryId === context.scope.parent?.comment?.discoveryId) {
delete sigRef.comment;
}
}
const parameterSymbols = signature.thisParameter
? [signature.thisParameter, ...signature.parameters]
: [...signature.parameters];
// Prevent a `this` parameter from appearing on constructor signature
// as TS disallows them on regular classes.
if (parameterSymbols[0]?.name === "this") {
parameterSymbols.shift();
}
sigRef.parameters = convertParameters(sigRefCtx, sigRef, parameterSymbols, declaration?.parameters);
sigRef.parameters = convertParameters(sigRefCtx, sigRef, parameterSymbols, undefined);
// Do NOT convert type parameters here, they go on the class itself in the @class case
// See #2914.
sigRef.type = ReferenceType.createResolvedReference(context.scope.parent.name, classType, context.project);
context.registerReflection(sigRef, undefined);
context.scope.signatures ??= [];
context.scope.signatures.push(sigRef);
context.converter.trigger(ConverterEvents.CREATE_SIGNATURE, context, sigRef, declaration, signature);
}
function convertParameters(context, sigRef, parameters, parameterNodes) {
// #2698 if `satisfies` is used to imply a this parameter, we might have
// more parameters than parameter nodes and need to shift the parameterNode
// access index. Very ugly, but it does the job.
const parameterNodeOffset = parameterNodes?.length !== parameters.length ? -1 : 0;
return parameters.map((param, i) => {
const declaration = param.valueDeclaration;
assert(!declaration ||
ts.isParameter(declaration) ||
ts.isJSDocParameterTag(declaration));
const paramRefl = new ParameterReflection(/__\d+/.test(param.name) ? "__namedParameters" : param.name, ReflectionKind.Parameter, sigRef);
if (declaration && ts.isJSDocParameterTag(declaration)) {
paramRefl.comment = context.getJsDocComment(declaration);
}
paramRefl.comment ||= context.getComment(param, paramRefl.kind);
context.registerReflection(paramRefl, param);
context.converter.trigger(ConverterEvents.CREATE_PARAMETER, context, paramRefl);
let type;
let typeNode;
if (declaration) {
type = context.checker.getTypeOfSymbolAtLocation(param, declaration);
if (ts.isParameter(declaration)) {
typeNode = declaration.type;
}
else {
typeNode = declaration.typeExpression?.type;
}
}
else {
type = param.type;
}
if (declaration &&
ts.isParameter(declaration) &&
declaration.type?.kind === ts.SyntaxKind.ThisType) {
paramRefl.type = new IntrinsicType("this");
}
else if (!type) {
paramRefl.type = new IntrinsicType("any");
}
else {
paramRefl.type = context.converter.convertType(context.withScope(paramRefl), type, typeNode);
}
let isOptional = false;
if (declaration) {
isOptional = ts.isParameter(declaration)
? !!declaration.questionToken ||
ts
.getJSDocParameterTags(declaration)
.some((tag) => tag.isBracketed)
: declaration.isBracketed;
}
if (isOptional) {
paramRefl.type = removeUndefined(paramRefl.type);
}
paramRefl.defaultValue = convertDefaultValue(parameterNodes?.[i + parameterNodeOffset]);
paramRefl.setFlag(ReflectionFlag.Optional, isOptional);
// If we have no declaration, then this is an implicitly defined parameter in JS land
// because the method body uses `arguments`... which is always a rest argument
let isRest = true;
if (declaration) {
isRest = ts.isParameter(declaration)
? !!declaration.dotDotDotToken
: !!declaration.typeExpression &&
ts.isJSDocVariadicType(declaration.typeExpression.type);
}
paramRefl.setFlag(ReflectionFlag.Rest, isRest);
checkForDestructuredParameterDefaults(paramRefl, parameterNodes?.[i + parameterNodeOffset]);
return paramRefl;
});
}
export function convertParameterNodes(context, sigRef, parameters) {
return parameters.map((param) => {
const paramRefl = new ParameterReflection(/__\d+/.test(param.name.getText())
? "__namedParameters"
: param.name.getText(), ReflectionKind.Parameter, sigRef);
if (ts.isJSDocParameterTag(param)) {
paramRefl.comment = context.getJsDocComment(param);
}
context.registerReflection(paramRefl, context.getSymbolAtLocation(param));
context.converter.trigger(ConverterEvents.CREATE_PARAMETER, context, paramRefl);
paramRefl.type = context.converter.convertType(context.withScope(paramRefl), ts.isParameter(param) ? param.type : param.typeExpression?.type);
const isOptional = ts.isParameter(param)
? !!param.questionToken
: param.isBracketed;
if (isOptional) {
paramRefl.type = removeUndefined(paramRefl.type);
}
paramRefl.defaultValue = convertDefaultValue(param);
paramRefl.setFlag(ReflectionFlag.Optional, isOptional);
paramRefl.setFlag(ReflectionFlag.Rest, ts.isParameter(param)
? !!param.dotDotDotToken
: !!param.typeExpression &&
ts.isJSDocVariadicType(param.typeExpression.type));
checkForDestructuredParameterDefaults(paramRefl, param);
return paramRefl;
});
}
function checkForDestructuredParameterDefaults(param, decl) {
if (!decl || !ts.isParameter(decl))
return;
if (param.name !== "__namedParameters")
return;
if (!ts.isObjectBindingPattern(decl.name))
return;
if (param.type?.type !== "reflection")
return;
for (const child of param.type.declaration.children || []) {
const tsChild = decl.name.elements.find((el) => (el.propertyName || el.name).getText() === child.name);
if (tsChild) {
child.defaultValue = convertDefaultValue(tsChild);
}
}
}
export function convertTypeParameters(context, parent, parameters) {
return parameters?.map((param) => {
const constraintT = param.getConstraint();
const defaultT = param.getDefault();
// There's no way to determine directly from a ts.TypeParameter what it's variance modifiers are
// so unfortunately we have to go back to the node for this...
const declaration = param
.getSymbol()
?.declarations?.find(ts.isTypeParameterDeclaration);
const variance = getVariance(declaration?.modifiers);
const paramRefl = new TypeParameterReflection(param.symbol.name, parent, variance);
const paramCtx = context.withScope(paramRefl);
paramRefl.type = constraintT
? context.converter.convertType(paramCtx, constraintT)
: void 0;
paramRefl.default = defaultT
? context.converter.convertType(paramCtx, defaultT)
: void 0;
// No way to determine this from the type parameter itself, need to go back to the declaration
if (declaration?.modifiers?.some((m) => m.kind === ts.SyntaxKind.ConstKeyword)) {
paramRefl.flags.setFlag(ReflectionFlag.Const, true);
}
context.registerReflection(paramRefl, param.getSymbol());
context.converter.trigger(ConverterEvents.CREATE_TYPE_PARAMETER, context, paramRefl);
return paramRefl;
});
}
export function convertTypeParameterNodes(context, parameters) {
return parameters?.map((param) => createTypeParamReflection(param, context));
}
export function createTypeParamReflection(param, context) {
const paramRefl = new TypeParameterReflection(param.name.text, context.scope, getVariance(param.modifiers));
const paramScope = context.withScope(paramRefl);
if (ts.isJSDocTemplateTag(param.parent)) {
// With a @template tag, the constraint applies only to the
// first type parameter declared.
if (param.parent.typeParameters[0].name.text === param.name.text && param.parent.constraint) {
paramRefl.type = context.converter.convertType(paramScope, param.parent.constraint);
}
}
else {
paramRefl.type = param.constraint
? context.converter.convertType(paramScope, param.constraint)
: void 0;
}
paramRefl.default = param.default
? context.converter.convertType(paramScope, param.default)
: void 0;
if (param.modifiers?.some((m) => m.kind === ts.SyntaxKind.ConstKeyword)) {
paramRefl.flags.setFlag(ReflectionFlag.Const, true);
}
context.registerReflection(paramRefl, param.symbol);
if (ts.isJSDocTemplateTag(param.parent)) {
paramRefl.comment = context.getJsDocComment(param.parent);
}
context.converter.trigger(ConverterEvents.CREATE_TYPE_PARAMETER, context, paramRefl, param);
return paramRefl;
}
export function convertTemplateParameterNodes(context, nodes) {
return nodes?.flatMap((node) => {
return node.typeParameters.map((param, index) => {
const paramRefl = new TypeParameterReflection(param.name.text, context.scope, getVariance(param.modifiers));
const paramScope = context.withScope(paramRefl);
paramRefl.type = index || !node.constraint
? void 0
: context.converter.convertType(paramScope, node.constraint.type);
paramRefl.default = param.default
? context.converter.convertType(paramScope, param.default)
: void 0;
if (param.modifiers?.some((m) => m.kind === ts.SyntaxKind.ConstKeyword)) {
paramRefl.flags.setFlag(ReflectionFlag.Const, true);
}
context.registerReflection(paramRefl, param.symbol);
if (ts.isJSDocTemplateTag(param.parent)) {
paramRefl.comment = context.getJsDocComment(param.parent);
}
context.converter.trigger(ConverterEvents.CREATE_TYPE_PARAMETER, context, paramRefl, param);
return paramRefl;
});
});
}
function getVariance(modifiers) {
const hasIn = modifiers?.some((mod) => mod.kind === ts.SyntaxKind.InKeyword);
const hasOut = modifiers?.some((mod) => mod.kind === ts.SyntaxKind.OutKeyword);
if (hasIn && hasOut) {
return VarianceModifier.inOut;
}
if (hasIn) {
return VarianceModifier.in;
}
if (hasOut) {
return VarianceModifier.out;
}
}
function convertPredicate(predicate, context) {
let name;
switch (predicate.kind) {
case ts.TypePredicateKind.This:
case ts.TypePredicateKind.AssertsThis:
name = "this";
break;
case ts.TypePredicateKind.Identifier:
case ts.TypePredicateKind.AssertsIdentifier:
name = predicate.parameterName;
break;
}
let asserts;
switch (predicate.kind) {
case ts.TypePredicateKind.This:
case ts.TypePredicateKind.Identifier:
asserts = false;
break;
case ts.TypePredicateKind.AssertsThis:
case ts.TypePredicateKind.AssertsIdentifier:
asserts = true;
break;
}
return new PredicateType(name, asserts, predicate.type
? context.converter.convertType(context, predicate.type)
: void 0);
}