UNPKG

typedoc

Version:

Create api documentation for TypeScript projects.

341 lines (340 loc) 15.8 kB
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); }