UNPKG

typedoc

Version:

Create api documentation for TypeScript projects.

729 lines (728 loc) 36.3 kB
import assert from "assert"; import ts from "typescript"; import { DeclarationReflection, IntrinsicType, LiteralType, ReferenceReflection, ReflectionFlag, ReflectionKind, } from "../models/index.js"; import { getEnumFlags, hasAllFlags, hasAnyFlag, removeFlag, } from "../utils/enum.js"; import { convertDefaultValue } from "./convert-expression.js"; import { convertIndexSignatures } from "./factories/index-signature.js"; import { createConstructSignatureWithType, createSignature, createTypeParamReflection, } from "./factories/signature.js"; import { convertJsDocAlias, convertJsDocCallback } from "./jsdoc.js"; import { getHeritageTypes } from "./utils/nodes.js"; import { removeUndefined } from "./utils/reflections.js"; const symbolConverters = { [ts.SymbolFlags.RegularEnum]: convertEnum, [ts.SymbolFlags.ConstEnum]: convertEnum, [ts.SymbolFlags.EnumMember]: convertEnumMember, [ts.SymbolFlags.ValueModule]: convertNamespace, [ts.SymbolFlags.NamespaceModule]: convertNamespace, [ts.SymbolFlags.TypeAlias]: convertTypeAlias, [ts.SymbolFlags.Function]: convertFunctionOrMethod, [ts.SymbolFlags.Method]: convertFunctionOrMethod, [ts.SymbolFlags.Interface]: convertClassOrInterface, [ts.SymbolFlags.Property]: convertProperty, [ts.SymbolFlags.Class]: convertClassOrInterface, [ts.SymbolFlags.Constructor]: convertConstructor, [ts.SymbolFlags.Alias]: convertAlias, [ts.SymbolFlags.BlockScopedVariable]: convertVariable, [ts.SymbolFlags.FunctionScopedVariable]: convertVariable, [ts.SymbolFlags.ExportValue]: convertVariable, [ts.SymbolFlags.GetAccessor]: convertAccessor, [ts.SymbolFlags.SetAccessor]: convertAccessor, }; const allConverterFlags = Object.keys(symbolConverters).reduce((v, k) => v | +k, 0); // This is kind of a hack, born of resolving references by symbols instead // of by source location. const conversionOrder = [ // Do enums before namespaces so that @hidden on a namespace // merged with an enum works properly. ts.SymbolFlags.RegularEnum, ts.SymbolFlags.ConstEnum, ts.SymbolFlags.EnumMember, // Before type alias ts.SymbolFlags.BlockScopedVariable, ts.SymbolFlags.FunctionScopedVariable, ts.SymbolFlags.ExportValue, ts.SymbolFlags.Function, // Before NamespaceModule ts.SymbolFlags.TypeAlias, ts.SymbolFlags.Method, ts.SymbolFlags.Interface, ts.SymbolFlags.Property, ts.SymbolFlags.Class, ts.SymbolFlags.Constructor, ts.SymbolFlags.Alias, ts.SymbolFlags.GetAccessor, ts.SymbolFlags.SetAccessor, ts.SymbolFlags.ValueModule, ts.SymbolFlags.NamespaceModule, ]; // Sanity check, if this fails a dev messed up. for (const key of Object.keys(symbolConverters)) { if (!Number.isInteger(Math.log2(+key))) { throw new Error(`Symbol converter for key ${ts.SymbolFlags[+key]} does not specify a valid flag value.`); } if (!conversionOrder.includes(+key)) { throw new Error(`Symbol converter for key ${ts.SymbolFlags[+key]} is not specified in conversionOrder`); } } if (conversionOrder.reduce((a, b) => a | b, 0) !== allConverterFlags) { throw new Error("conversionOrder contains a symbol flag that converters do not."); } export function convertSymbol(context, symbol, exportSymbol) { if (context.shouldIgnore(symbol)) { return; } // This check can catch symbols which ought to be documented as references // but aren't aliased symbols because `export *` was used. const previous = context.project.getReflectionFromSymbol(symbol); if (previous && previous.parent?.kindOf(ReflectionKind.SomeModule | ReflectionKind.Project)) { createAlias(previous, context, symbol, exportSymbol); return; } let flags = removeFlag(symbol.flags, ts.SymbolFlags.Transient | ts.SymbolFlags.Assignment | ts.SymbolFlags.Optional | ts.SymbolFlags.Prototype); // Declaration merging - the only type (excluding enum/enum, ns/ns, etc) // that TD supports is merging a class and interface. All others are // represented as multiple reflections if (hasAllFlags(symbol.flags, ts.SymbolFlags.Class)) { flags = removeFlag(flags, ts.SymbolFlags.Interface | ts.SymbolFlags.Function); } // Kind of declaration merging... we treat this as a property with get/set signatures. if (hasAllFlags(symbol.flags, ts.SymbolFlags.GetAccessor)) { flags = removeFlag(flags, ts.SymbolFlags.SetAccessor); } if (hasAllFlags(symbol.flags, ts.SymbolFlags.NamespaceModule)) { // This might be here if a namespace is declared several times. // Or if it's a namespace-like thing defined on a function // In the function case, it's important to remove ValueModule so that // if we convert the children as properties of the function rather than as // a separate namespace, we skip creating the namespace. flags = removeFlag(flags, ts.SymbolFlags.ValueModule); } if (hasAnyFlag(symbol.flags, ts.SymbolFlags.Method | ts.SymbolFlags.Interface | ts.SymbolFlags.Class | ts.SymbolFlags.Variable)) { // This happens when someone declares an object with methods: // { methodProperty() {} } flags = removeFlag(flags, ts.SymbolFlags.Property); } for (const flag of getEnumFlags(flags & ~allConverterFlags)) { context.logger.verbose(`Missing converter for symbol: ${symbol.name} with flag ${ts.SymbolFlags[flag]}`); } // Note: This method does not allow skipping earlier converters. // For now, this is fine... might not be flexible enough in the future. let skip = 0; for (const flag of conversionOrder) { if (!(flag & flags)) continue; if (skip & flag) continue; skip |= symbolConverters[flag]?.(context, symbol, exportSymbol) || 0; } } function convertSymbols(context, symbols) { for (const symbol of symbols) { convertSymbol(context, symbol); } } function convertEnum(context, symbol, exportSymbol) { const reflection = context.createDeclarationReflection(ReflectionKind.Enum, symbol, exportSymbol); if (symbol.flags & ts.SymbolFlags.ConstEnum) { reflection.setFlag(ReflectionFlag.Const); } context.finalizeDeclarationReflection(reflection); convertSymbols(context.withScope(reflection), context.checker .getExportsOfModule(symbol) .filter((s) => s.flags & ts.SymbolFlags.EnumMember)); } function convertEnumMember(context, symbol, exportSymbol) { const reflection = context.createDeclarationReflection(ReflectionKind.EnumMember, symbol, exportSymbol); const defaultValue = context.checker.getConstantValue(symbol.getDeclarations()[0]); if (defaultValue !== undefined) { reflection.type = new LiteralType(defaultValue); } else { // We know this has to be a number, because computed values aren't allowed // in string enums, so otherwise we would have to have the constant value reflection.type = new IntrinsicType("number"); } context.finalizeDeclarationReflection(reflection); } function convertNamespace(context, symbol, exportSymbol) { let exportFlags = ts.SymbolFlags.ModuleMember; // This can happen in JS land where "class" functions get tagged as a namespace too if (symbol .getDeclarations() ?.some((d) => ts.isModuleDeclaration(d) || ts.isSourceFile(d)) !== true) { exportFlags = ts.SymbolFlags.ClassMember; if (hasAnyFlag(symbol.flags, ts.SymbolFlags.Class)) { return; } } // #2364, @namespace on a variable might be merged with a namespace containing types. const existingReflection = context.project.getReflectionFromSymbol(exportSymbol || symbol); let reflection; if (existingReflection?.kind === ReflectionKind.Namespace) { reflection = existingReflection; } else { let kind = ReflectionKind.Namespace; let nameOverride; // #2778 - always treat declare module "foo" as a module, not a namespace const declareModule = symbol.declarations?.find((mod) => ts.isModuleDeclaration(mod) && ts.isStringLiteral(mod.name)); if (declareModule) { kind = ReflectionKind.Module; nameOverride = declareModule.name.text; } reflection = context.createDeclarationReflection(kind, symbol, exportSymbol, nameOverride); context.finalizeDeclarationReflection(reflection); } convertSymbols(context.withScope(reflection), context.checker .getExportsOfModule(symbol) .filter((s) => s.flags & exportFlags)); } function convertTypeAlias(context, symbol, exportSymbol) { const declaration = symbol .getDeclarations() ?.find((d) => ts.isTypeAliasDeclaration(d) || ts.isJSDocTypedefTag(d) || ts.isJSDocCallbackTag(d) || ts.isJSDocEnumTag(d)); assert(declaration); if (ts.isTypeAliasDeclaration(declaration)) { if (context .getComment(symbol, ReflectionKind.TypeAlias) ?.hasModifier("@interface")) { return convertTypeAliasAsInterface(context, symbol, exportSymbol, declaration); } const reflection = context.createDeclarationReflection(ReflectionKind.TypeAlias, symbol, exportSymbol); if (reflection.comment?.hasModifier("@useDeclaredType")) { reflection.comment.removeModifier("@useDeclaredType"); reflection.type = context.converter.convertType(context.withScope(reflection), context.checker.getDeclaredTypeOfSymbol(symbol)); } else { reflection.type = context.converter.convertType(context.withScope(reflection), declaration.type); } if (reflection.type.type === "union") { attachUnionComments(context, declaration, reflection.type); } context.finalizeDeclarationReflection(reflection); // Do this after finalization so that the CommentPlugin can get @typeParam tags // from the parent comment. Ugly, but works for now. Should be cleaned up eventually. reflection.typeParameters = declaration.typeParameters?.map((param) => createTypeParamReflection(param, context.withScope(reflection))); } else if (ts.isJSDocTypedefTag(declaration) || ts.isJSDocEnumTag(declaration)) { convertJsDocAlias(context, symbol, declaration, exportSymbol); } else { convertJsDocCallback(context, symbol, declaration, exportSymbol); } } function attachUnionComments(context, declaration, union) { const list = declaration.type.getChildAt(0); if (list.kind !== ts.SyntaxKind.SyntaxList) return; let unionIndex = 0; for (const child of list.getChildren()) { const comment = context.getNodeComment(child, false); if (comment?.modifierTags.size || comment?.blockTags.length) { context.logger.warn(context.logger.i18n.comment_for_0_should_not_contain_block_or_modifier_tags(`${context.scope.getFriendlyFullName()}.${unionIndex}`), child); } if (comment) { union.elementSummaries ||= Array.from({ length: union.types.length }, () => []); union.elementSummaries[unionIndex] = comment.summary; } if (child.kind !== ts.SyntaxKind.BarToken) { ++unionIndex; } } } function convertTypeAliasAsInterface(context, symbol, exportSymbol, declaration) { const reflection = context.createDeclarationReflection(ReflectionKind.Interface, symbol, exportSymbol); context.finalizeDeclarationReflection(reflection); const rc = context.withScope(reflection); const type = context.checker.getTypeAtLocation(declaration); if (type.getFlags() & ts.TypeFlags.Union) { context.logger.warn(context.i18n.converting_union_as_interface(), declaration); } // Interfaces have properties convertSymbols(rc, type.getProperties()); // And type arguments if (declaration.typeParameters) { reflection.typeParameters = declaration.typeParameters.map((param) => { const declaration = param.symbol?.declarations?.[0]; assert(declaration && ts.isTypeParameterDeclaration(declaration)); return createTypeParamReflection(declaration, rc); }); } // And maybe call signatures context.checker .getSignaturesOfType(type, ts.SignatureKind.Call) .forEach((sig) => createSignature(rc, ReflectionKind.CallSignature, sig, symbol)); // And maybe constructor signatures convertConstructSignatures(rc, symbol); // And finally, index signatures convertIndexSignatures(rc, type); } function convertFunctionOrMethod(context, symbol, exportSymbol) { // Can't just check method flag because this might be called for properties as well // This will *NOT* be called for variables that look like functions, they need a special case. const isMethod = !!(symbol.flags & (ts.SymbolFlags.Property | ts.SymbolFlags.Method)); if (!isMethod) { const comment = context.getComment(symbol, ReflectionKind.Function); if (comment?.hasModifier("@class")) { return convertSymbolAsClass(context, symbol, exportSymbol); } } const declarations = symbol.getDeclarations()?.filter(ts.isFunctionLike) ?? []; // Don't do anything if we inherited this method and it is private. if (isMethod && isInherited(context, symbol) && declarations.length > 0 && hasAllFlags(ts.getCombinedModifierFlags(declarations[0]), ts.ModifierFlags.Private)) { return; } const locationDeclaration = symbol.parent ?.getDeclarations() ?.find((d) => ts.isClassDeclaration(d) || ts.isInterfaceDeclaration(d)) ?? symbol.parent?.getDeclarations()?.[0]?.getSourceFile() ?? symbol.getDeclarations()?.[0]?.getSourceFile(); assert(locationDeclaration, "Missing declaration context"); const type = context.checker.getTypeOfSymbolAtLocation(symbol, locationDeclaration); // Need to get the non nullable type because interface methods might be declared // with a question token. See GH1490. const signatures = type.getNonNullableType().getCallSignatures(); const reflection = context.createDeclarationReflection(context.scope.kindOf(ReflectionKind.MethodContainer) ? ReflectionKind.Method : ReflectionKind.Function, symbol, exportSymbol, void 0); if (symbol.declarations?.length && isMethod) { // All method signatures must have the same modifier flags. setModifiers(symbol, symbol.declarations[0], reflection); } context.finalizeDeclarationReflection(reflection); const scope = context.withScope(reflection); for (const sig of signatures) { createSignature(scope, ReflectionKind.CallSignature, sig, symbol); } return convertFunctionProperties(scope, symbol, type); } // getDeclaredTypeOfSymbol gets the INSTANCE type // getTypeOfSymbolAtLocation gets the STATIC type function convertClassOrInterface(context, symbol, exportSymbol) { const reflection = context.createDeclarationReflection(ts.SymbolFlags.Class & symbol.flags ? ReflectionKind.Class : ReflectionKind.Interface, symbol, exportSymbol, void 0); const classDeclaration = symbol .getDeclarations() ?.find((d) => ts.isClassDeclaration(d) || ts.isFunctionDeclaration(d)); if (classDeclaration) setModifiers(symbol, classDeclaration, reflection); const reflectionContext = context.withScope(reflection); reflectionContext.convertingClassOrInterface = true; const instanceType = context.checker.getDeclaredTypeOfSymbol(symbol); assert(instanceType.isClassOrInterface()); // We might do some inheritance - do this first so that it's set when converting properties const declarations = symbol .getDeclarations() ?.filter((d) => ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) ?? []; const extendedTypes = getHeritageTypes(declarations, ts.SyntaxKind.ExtendsKeyword).map((t) => context.converter.convertType(reflectionContext, t)); if (extendedTypes.length) { reflection.extendedTypes = extendedTypes; } const implementedTypes = getHeritageTypes(declarations, ts.SyntaxKind.ImplementsKeyword).map((t) => context.converter.convertType(reflectionContext, t)); if (implementedTypes.length) { reflection.implementedTypes = implementedTypes; } context.finalizeDeclarationReflection(reflection); if (classDeclaration) { // Classes can have static props const staticType = context.checker.getTypeOfSymbolAtLocation(symbol, classDeclaration); reflectionContext.shouldBeStatic = true; for (const prop of context.checker.getPropertiesOfType(staticType)) { // Don't convert namespace members, or the prototype here. if (prop.flags & (ts.SymbolFlags.ModuleMember | ts.SymbolFlags.Prototype)) continue; convertSymbol(reflectionContext, prop); } reflectionContext.shouldBeStatic = false; const ctors = staticType.getConstructSignatures(); const constructMember = reflectionContext.createDeclarationReflection(ReflectionKind.Constructor, ctors[0]?.declaration?.symbol, void 0, "constructor"); // Modifiers are the same for all constructors if (ctors.length && ctors[0].declaration) { setModifiers(symbol, ctors[0].declaration, constructMember); } context.finalizeDeclarationReflection(constructMember); const constructContext = reflectionContext.withScope(constructMember); ctors.forEach((sig) => { createSignature(constructContext, ReflectionKind.ConstructorSignature, sig, symbol); }); } // Classes/interfaces usually just have properties... convertSymbols(reflectionContext, context.checker.getPropertiesOfType(instanceType)); // And type arguments if (instanceType.typeParameters) { reflection.typeParameters = instanceType.typeParameters.map((param) => { const declaration = param.symbol.declarations?.[0]; assert(declaration && ts.isTypeParameterDeclaration(declaration)); return createTypeParamReflection(declaration, reflectionContext); }); } // Interfaces might also have call signatures // Classes might too, because of declaration merging context.checker .getSignaturesOfType(instanceType, ts.SignatureKind.Call) .forEach((sig) => createSignature(reflectionContext, ReflectionKind.CallSignature, sig, symbol)); // We also might have constructor signatures // This is potentially a problem with classes having multiple "constructor" members... // but nobody has complained yet. convertConstructSignatures(reflectionContext, symbol); // And finally, index signatures convertIndexSignatures(reflectionContext, instanceType); // Normally this shouldn't matter, unless someone did something with skipLibCheck on. return ts.SymbolFlags.Alias; } function convertProperty(context, symbol, exportSymbol) { // This might happen if we're converting a function-module created with Object.assign // or `export default () => {}` if (context.scope.kindOf(ReflectionKind.VariableContainer)) { return convertVariable(context, symbol, exportSymbol); } const declarations = symbol.getDeclarations() ?? []; // Don't do anything if we inherited this property and it is private. if (isInherited(context, symbol) && declarations.length > 0 && hasAllFlags(ts.getCombinedModifierFlags(declarations[0]), ts.ModifierFlags.Private)) { return; } // Special case: We pretend properties are methods if they look like methods. // This happens with mixins / weird inheritance. if (declarations.length && declarations.every((decl) => ts.isMethodSignature(decl) || ts.isMethodDeclaration(decl))) { return convertFunctionOrMethod(context, symbol, exportSymbol); } if (declarations.length === 1) { const declaration = declarations[0]; // Special case: "arrow methods" should be treated as methods. if (ts.isPropertyDeclaration(declaration) && !declaration.type && declaration.initializer && ts.isArrowFunction(declaration.initializer)) { return convertArrowAsMethod(context, symbol, declaration.initializer, exportSymbol); } } const reflection = context.createDeclarationReflection(context.scope.kindOf(ReflectionKind.VariableContainer) ? ReflectionKind.Variable : ReflectionKind.Property, symbol, exportSymbol); const declaration = symbol.getDeclarations()?.[0]; let parameterType; if (declaration && (ts.isPropertyDeclaration(declaration) || ts.isPropertySignature(declaration) || ts.isParameter(declaration) || ts.isPropertyAccessExpression(declaration) || ts.isPropertyAssignment(declaration))) { if (!ts.isPropertyAccessExpression(declaration) && !ts.isPropertyAssignment(declaration)) { parameterType = declaration.type; } setModifiers(symbol, declaration, reflection); } else { setSymbolModifiers(symbol, reflection); } reflection.defaultValue = declaration && convertDefaultValue(declaration); reflection.type = context.converter.convertType(context.withScope(reflection), (context.convertingTypeNode ? parameterType : void 0) ?? context.checker.getTypeOfSymbol(symbol)); if (reflection.flags.isOptional) { reflection.type = removeUndefined(reflection.type); } context.finalizeDeclarationReflection(reflection); } function convertArrowAsMethod(context, symbol, arrow, exportSymbol) { const reflection = context.createDeclarationReflection(ReflectionKind.Method, symbol, exportSymbol, void 0); setModifiers(symbol, arrow.parent, reflection); context.finalizeDeclarationReflection(reflection); const rc = context.withScope(reflection); const locationDeclaration = symbol.parent ?.getDeclarations() ?.find((d) => ts.isClassDeclaration(d) || ts.isInterfaceDeclaration(d)) ?? symbol.parent?.getDeclarations()?.[0]?.getSourceFile() ?? symbol.getDeclarations()?.[0]?.getSourceFile(); assert(locationDeclaration, "Missing declaration context"); const type = context.checker.getTypeOfSymbolAtLocation(symbol, locationDeclaration); const signatures = type.getNonNullableType().getCallSignatures(); assert(signatures.length, "Missing signatures"); createSignature(rc, ReflectionKind.CallSignature, signatures[0], symbol, arrow); } function convertConstructor(context, symbol) { const reflection = context.createDeclarationReflection(ReflectionKind.Constructor, symbol, void 0, "constructor"); context.finalizeDeclarationReflection(reflection); const reflectionContext = context.withScope(reflection); const declarations = symbol.getDeclarations()?.filter(ts.isConstructorDeclaration) ?? []; const signatures = declarations.map((decl) => { const sig = context.checker.getSignatureFromDeclaration(decl); assert(sig); return sig; }); for (const sig of signatures) { createSignature(reflectionContext, ReflectionKind.ConstructorSignature, sig, symbol); } } function convertConstructSignatures(context, symbol) { const type = context.checker.getDeclaredTypeOfSymbol(symbol); // These get added as a "constructor" member of this interface. This is a problem... but nobody // has complained yet. We really ought to have a constructSignatures property on the reflection instead. const constructSignatures = context.checker.getSignaturesOfType(type, ts.SignatureKind.Construct); if (constructSignatures.length) { const constructMember = new DeclarationReflection("constructor", ReflectionKind.Constructor, context.scope); context.postReflectionCreation(constructMember, symbol, void 0); context.finalizeDeclarationReflection(constructMember); const constructContext = context.withScope(constructMember); constructSignatures.forEach((sig) => createSignature(constructContext, ReflectionKind.ConstructorSignature, sig, symbol)); } } function convertAlias(context, symbol, exportSymbol) { const reflection = context.project.getReflectionFromSymbol(context.resolveAliasedSymbol(symbol)); if (!reflection) { // We don't have this, convert it. convertSymbol(context, context.resolveAliasedSymbol(symbol), exportSymbol ?? symbol); } else { createAlias(reflection, context, symbol, exportSymbol); } } function createAlias(target, context, symbol, exportSymbol) { if (context.converter.excludeReferences) return; // We already have this. Create a reference. const ref = new ReferenceReflection(exportSymbol?.name ?? symbol.name, target.isReference() ? target.getTargetReflection() : target, context.scope); context.postReflectionCreation(ref, symbol, exportSymbol); context.finalizeDeclarationReflection(ref); } function convertVariable(context, symbol, exportSymbol) { const declaration = symbol.getDeclarations()?.[0]; const comment = context.getComment(symbol, ReflectionKind.Variable); const type = declaration ? context.checker.getTypeOfSymbolAtLocation(symbol, declaration) : context.checker.getTypeOfSymbol(symbol); if (isEnumLike(context.checker, type, declaration) && comment?.hasModifier("@enum")) { return convertVariableAsEnum(context, symbol, exportSymbol); } if (comment?.hasModifier("@namespace")) { return convertVariableAsNamespace(context, symbol, exportSymbol); } if (comment?.hasModifier("@class")) { return convertSymbolAsClass(context, symbol, exportSymbol); } if (type.getCallSignatures().length && !type.getConstructSignatures().length) { return convertVariableAsFunction(context, symbol, exportSymbol); } const reflection = context.createDeclarationReflection(context.scope.kindOf(ReflectionKind.VariableContainer) ? ReflectionKind.Variable : ReflectionKind.Property, symbol, exportSymbol); let typeNode; if (declaration && ts.isVariableDeclaration(declaration)) { // Otherwise we might have destructuring typeNode = declaration.type; } reflection.type = context.converter.convertType(context.withScope(reflection), typeNode ?? type); setModifiers(symbol, declaration, reflection); reflection.defaultValue = convertDefaultValue(declaration); context.finalizeDeclarationReflection(reflection); return ts.SymbolFlags.Property; } function isEnumLike(checker, type, location) { if (!location || !hasAllFlags(type.flags, ts.TypeFlags.Object)) { return false; } return type.getProperties().every((prop) => { const propType = checker.getTypeOfSymbolAtLocation(prop, location); return isValidEnumProperty(propType); }); } function isValidEnumProperty(type) { return hasAnyFlag(type.flags, ts.TypeFlags.NumberLike | ts.TypeFlags.StringLike); } function convertVariableAsEnum(context, symbol, exportSymbol) { const reflection = context.createDeclarationReflection(ReflectionKind.Enum, symbol, exportSymbol); context.finalizeDeclarationReflection(reflection); const rc = context.withScope(reflection); const declaration = symbol.declarations.find(ts.isVariableDeclaration); const type = context.checker.getTypeAtLocation(declaration); for (const prop of type.getProperties()) { const reflection = rc.createDeclarationReflection(ReflectionKind.EnumMember, prop, void 0); const propType = context.checker.getTypeOfSymbolAtLocation(prop, declaration); reflection.type = context.converter.convertType(rc.withScope(reflection), propType); rc.finalizeDeclarationReflection(reflection); } // Skip converting the type alias, if there is one return ts.SymbolFlags.TypeAlias; } function convertVariableAsNamespace(context, symbol, exportSymbol) { const reflection = context.createDeclarationReflection(ReflectionKind.Namespace, symbol, exportSymbol); context.finalizeDeclarationReflection(reflection); const rc = context.withScope(reflection); const type = context.checker.getTypeOfSymbol(symbol); convertSymbols(rc, type.getProperties()); return ts.SymbolFlags.Property; } function convertVariableAsFunction(context, symbol, exportSymbol) { const declaration = symbol .getDeclarations() ?.find(ts.isVariableDeclaration); const accessDeclaration = declaration ?? symbol.valueDeclaration; const type = accessDeclaration ? context.checker.getTypeOfSymbolAtLocation(symbol, accessDeclaration) : context.checker.getDeclaredTypeOfSymbol(symbol); const reflection = context.createDeclarationReflection(context.scope.kindOf(ReflectionKind.MethodContainer) ? ReflectionKind.Method : ReflectionKind.Function, symbol, exportSymbol); setModifiers(symbol, accessDeclaration, reflection); context.finalizeDeclarationReflection(reflection); const reflectionContext = context.withScope(reflection); reflection.signatures ??= []; for (const signature of type.getCallSignatures()) { createSignature(reflectionContext, ReflectionKind.CallSignature, signature, symbol); } return (convertFunctionProperties(context.withScope(reflection), symbol, type) | ts.SymbolFlags.Property); } function convertFunctionProperties(context, symbol, type) { // #2436/#2461: Functions created with Object.assign on a function likely have properties // that we should document. We also add properties to the function if they are defined like: // function f() {} // f.x = 123; // rather than creating a separate namespace. // In the expando case, we'll have both namespace flags. // In the Object.assign case, we'll have no namespace flags. const nsFlags = ts.SymbolFlags.ValueModule | ts.SymbolFlags.NamespaceModule; if (type.getProperties().length && (hasAllFlags(symbol.flags, nsFlags) || !hasAnyFlag(symbol.flags, nsFlags))) { convertSymbols(context, type.getProperties()); return ts.SymbolFlags.NamespaceModule; } return ts.SymbolFlags.None; } function convertSymbolAsClass(context, symbol, exportSymbol) { const reflection = context.createDeclarationReflection(ReflectionKind.Class, symbol, exportSymbol); const rc = context.withScope(reflection); context.finalizeDeclarationReflection(reflection); if (!symbol.valueDeclaration) { context.logger.error(context.i18n.converting_0_as_class_requires_value_declaration(symbol.name), symbol.declarations?.[0]); return; } const type = context.checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration); rc.shouldBeStatic = true; convertSymbols(rc, // Prototype is implicitly this class, don't document it. type.getProperties().filter((prop) => prop.name !== "prototype")); for (const sig of type.getCallSignatures()) { createSignature(rc, ReflectionKind.CallSignature, sig, undefined); } rc.shouldBeStatic = false; const ctors = type.getConstructSignatures(); if (ctors.length) { const constructMember = rc.createDeclarationReflection(ReflectionKind.Constructor, ctors[0]?.declaration?.symbol, void 0, "constructor"); // Modifiers are the same for all constructors if (ctors.length && ctors[0].declaration) { setModifiers(symbol, ctors[0].declaration, constructMember); } context.finalizeDeclarationReflection(constructMember); const constructContext = rc.withScope(constructMember); for (const sig of ctors) { createConstructSignatureWithType(constructContext, sig, reflection); } const instType = ctors[0].getReturnType(); convertSymbols(rc, instType.getProperties()); for (const sig of instType.getCallSignatures()) { createSignature(rc, ReflectionKind.CallSignature, sig, undefined); } } else { context.logger.warn(context.i18n.converting_0_as_class_without_construct_signatures(reflection.getFriendlyFullName()), symbol.valueDeclaration); } return (ts.SymbolFlags.TypeAlias | ts.SymbolFlags.Interface | ts.SymbolFlags.Namespace); } function convertAccessor(context, symbol, exportSymbol) { const reflection = context.createDeclarationReflection(ReflectionKind.Accessor, symbol, exportSymbol); const rc = context.withScope(reflection); const declaration = symbol.getDeclarations()?.[0]; if (declaration) { setModifiers(symbol, declaration, reflection); } context.finalizeDeclarationReflection(reflection); const getDeclaration = symbol.getDeclarations()?.find(ts.isGetAccessor); if (getDeclaration) { const signature = context.checker.getSignatureFromDeclaration(getDeclaration); if (signature) { createSignature(rc, ReflectionKind.GetSignature, signature, symbol, getDeclaration); } } const setDeclaration = symbol.getDeclarations()?.find(ts.isSetAccessor); if (setDeclaration) { const signature = context.checker.getSignatureFromDeclaration(setDeclaration); if (signature) { createSignature(rc, ReflectionKind.SetSignature, signature, symbol, setDeclaration); } } } function isInherited(context, symbol) { const parentSymbol = context.project.getSymbolFromReflection(context.scope); // It'd be nice to be able to assert that this is true, but sometimes object // types don't get symbols if they are inferred. if (!parentSymbol) return false; const parents = parentSymbol.declarations?.slice() || []; const constructorDecls = parents.flatMap((parent) => ts.isClassDeclaration(parent) ? parent.members.filter(ts.isConstructorDeclaration) : []); parents.push(...constructorDecls); return (parents.some((d) => symbol.getDeclarations()?.some((d2) => d2.parent === d)) === false); } function setModifiers(symbol, declaration, reflection) { setSymbolModifiers(symbol, reflection); if (!declaration) { return; } const modifiers = ts.getCombinedModifierFlags(declaration); if (ts.isMethodDeclaration(declaration) || ts.isPropertyDeclaration(declaration) || ts.isAccessor(declaration)) { if (ts.isPrivateIdentifier(declaration.name)) { reflection.setFlag(ReflectionFlag.Private); } } if (hasAllFlags(modifiers, ts.ModifierFlags.Private)) { reflection.setFlag(ReflectionFlag.Private); } if (hasAllFlags(modifiers, ts.ModifierFlags.Protected)) { reflection.setFlag(ReflectionFlag.Protected); } if (hasAllFlags(modifiers, ts.ModifierFlags.Public)) { reflection.setFlag(ReflectionFlag.Public); } reflection.setFlag(ReflectionFlag.Optional, hasAllFlags(symbol.flags, ts.SymbolFlags.Optional)); reflection.setFlag(ReflectionFlag.Readonly, hasAllFlags(ts.getCheckFlags(symbol), ts.CheckFlags.Readonly) || hasAllFlags(modifiers, ts.ModifierFlags.Readonly)); reflection.setFlag(ReflectionFlag.Abstract, hasAllFlags(modifiers, ts.ModifierFlags.Abstract)); if (reflection.kindOf(ReflectionKind.Variable) && hasAllFlags(symbol.flags, ts.SymbolFlags.BlockScopedVariable)) { reflection.setFlag(ReflectionFlag.Const, hasAllFlags(declaration.parent.flags, ts.NodeFlags.Const)); } // ReflectionFlag.Static happens when constructing the reflection. // We don't have sufficient information here to determine if it ought to be static. } function setSymbolModifiers(symbol, reflection) { reflection.setFlag(ReflectionFlag.Optional, hasAllFlags(symbol.flags, ts.SymbolFlags.Optional)); }