UNPKG

typedoc

Version:

Create api documentation for TypeScript projects.

719 lines (718 loc) 37.6 kB
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) { function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value"; var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null; var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {}); var _, done = false; for (var i = decorators.length - 1; i >= 0; i--) { var context = {}; for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p]; for (var p in contextIn.access) context.access[p] = contextIn.access[p]; context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); }; var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); if (kind === "accessor") { if (result === void 0) continue; if (result === null || typeof result !== "object") throw new TypeError("Object expected"); if (_ = accept(result.get)) descriptor.get = _; if (_ = accept(result.set)) descriptor.set = _; if (_ = accept(result.init)) initializers.unshift(_); } else if (_ = accept(result)) { if (kind === "field") initializers.unshift(_); else descriptor[key] = _; } } if (target) Object.defineProperty(target, contextIn.name, descriptor); done = true; }; var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { var useValue = arguments.length > 2; for (var i = 0; i < initializers.length; i++) { value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg); } return useValue ? value : void 0; }; import { Comment, CommentTag, DeclarationReflection, ParameterReflection, ReflectionFlag, ReflectionKind, ReflectionType, SignatureReflection, } from "../../models/index.js"; import { Option, filterMap, partition, removeIf, removeIfPresent, unique, } from "../../utils/index.js"; import { setIntersection } from "../../utils/set.js"; import { ConverterComponent } from "../components.js"; import { ConverterEvents } from "../converter-events.js"; import { CategoryPlugin } from "./CategoryPlugin.js"; /** * These tags are not useful to display in the generated documentation. * They should be ignored when parsing comments. Any relevant type information * (for JS users) will be consumed by TypeScript and need not be preserved * in the comment. * * Note that param/arg/argument/return/returns are not present. * These tags will have their type information stripped when parsing, but still * provide useful information for documentation. */ const NEVER_RENDERED = [ "@augments", "@callback", "@class", "@constructor", "@enum", "@extends", "@this", "@type", "@typedef", "@jsx", ]; // We might make this user configurable at some point, but for now, // this set is configured here. const MUTUALLY_EXCLUSIVE_MODIFIERS = [ new Set([ "@alpha", "@beta", "@experimental", "@internal", "@public", ]), ]; /** * Handles most behavior triggered by comments. `@group` and `@category` are handled by their respective plugins, but everything else is here. * * How it works today * ================== * During conversion: * - Handle visibility flags (`@private`, `@protected`. `@public`) * - Handle module renames (`@module`) * - Remove excluded tags & comment discovery tags (`@module`, `@packageDocumentation`) * - Copy comments for type parameters from the parent container (for classes/interfaces) * * Resolve begin: * - Remove hidden reflections * * Resolve: * - Apply `@label` tag * - Copy comments on signature containers to the signature if signatures don't already have a comment * and then remove the comment on the container. * - Copy comments to parameters and type parameters (for signatures) * - Apply `@group` and `@category` tags * * Resolve end: * - Copy auto inherited comments from heritage clauses * - Handle `@inheritDoc` * - Resolve `@link` tags to point to target reflections * * How it should work * ================== * During conversion: * - Handle visibility flags (`@private`, `@protected`. `@public`) * - Handle module renames (`@module`) * - Remove excluded tags & comment discovery tags (`@module`, `@packageDocumentation`) * * Resolve begin (100): * - Copy auto inherited comments from heritage clauses * - Apply `@label` tag * * Resolve begin (75) * - Handle `@inheritDoc` * * Resolve begin (50) * - Copy comments on signature containers to the signature if signatures don't already have a comment * and then remove the comment on the container. * - Copy comments for type parameters from the parent container (for classes/interfaces) * * Resolve begin (25) * - Remove hidden reflections * * Resolve: * - Copy comments to parameters and type parameters (for signatures) * - Apply `@group` and `@category` tags * * Resolve end: * - Resolve `@link` tags to point to target reflections * */ let CommentPlugin = (() => { let _classSuper = ConverterComponent; let _excludeTags_decorators; let _excludeTags_initializers = []; let _excludeTags_extraInitializers = []; let _cascadedModifierTags_decorators; let _cascadedModifierTags_initializers = []; let _cascadedModifierTags_extraInitializers = []; let _excludeInternal_decorators; let _excludeInternal_initializers = []; let _excludeInternal_extraInitializers = []; let _excludePrivate_decorators; let _excludePrivate_initializers = []; let _excludePrivate_extraInitializers = []; let _excludeProtected_decorators; let _excludeProtected_initializers = []; let _excludeProtected_extraInitializers = []; let _excludeNotDocumented_decorators; let _excludeNotDocumented_initializers = []; let _excludeNotDocumented_extraInitializers = []; let _excludeCategories_decorators; let _excludeCategories_initializers = []; let _excludeCategories_extraInitializers = []; let _defaultCategory_decorators; let _defaultCategory_initializers = []; let _defaultCategory_extraInitializers = []; return class CommentPlugin extends _classSuper { static { const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0; _excludeTags_decorators = [Option("excludeTags")]; _cascadedModifierTags_decorators = [Option("cascadedModifierTags")]; _excludeInternal_decorators = [Option("excludeInternal")]; _excludePrivate_decorators = [Option("excludePrivate")]; _excludeProtected_decorators = [Option("excludeProtected")]; _excludeNotDocumented_decorators = [Option("excludeNotDocumented")]; _excludeCategories_decorators = [Option("excludeCategories")]; _defaultCategory_decorators = [Option("defaultCategory")]; __esDecorate(this, null, _excludeTags_decorators, { kind: "accessor", name: "excludeTags", static: false, private: false, access: { has: obj => "excludeTags" in obj, get: obj => obj.excludeTags, set: (obj, value) => { obj.excludeTags = value; } }, metadata: _metadata }, _excludeTags_initializers, _excludeTags_extraInitializers); __esDecorate(this, null, _cascadedModifierTags_decorators, { kind: "accessor", name: "cascadedModifierTags", static: false, private: false, access: { has: obj => "cascadedModifierTags" in obj, get: obj => obj.cascadedModifierTags, set: (obj, value) => { obj.cascadedModifierTags = value; } }, metadata: _metadata }, _cascadedModifierTags_initializers, _cascadedModifierTags_extraInitializers); __esDecorate(this, null, _excludeInternal_decorators, { kind: "accessor", name: "excludeInternal", static: false, private: false, access: { has: obj => "excludeInternal" in obj, get: obj => obj.excludeInternal, set: (obj, value) => { obj.excludeInternal = value; } }, metadata: _metadata }, _excludeInternal_initializers, _excludeInternal_extraInitializers); __esDecorate(this, null, _excludePrivate_decorators, { kind: "accessor", name: "excludePrivate", static: false, private: false, access: { has: obj => "excludePrivate" in obj, get: obj => obj.excludePrivate, set: (obj, value) => { obj.excludePrivate = value; } }, metadata: _metadata }, _excludePrivate_initializers, _excludePrivate_extraInitializers); __esDecorate(this, null, _excludeProtected_decorators, { kind: "accessor", name: "excludeProtected", static: false, private: false, access: { has: obj => "excludeProtected" in obj, get: obj => obj.excludeProtected, set: (obj, value) => { obj.excludeProtected = value; } }, metadata: _metadata }, _excludeProtected_initializers, _excludeProtected_extraInitializers); __esDecorate(this, null, _excludeNotDocumented_decorators, { kind: "accessor", name: "excludeNotDocumented", static: false, private: false, access: { has: obj => "excludeNotDocumented" in obj, get: obj => obj.excludeNotDocumented, set: (obj, value) => { obj.excludeNotDocumented = value; } }, metadata: _metadata }, _excludeNotDocumented_initializers, _excludeNotDocumented_extraInitializers); __esDecorate(this, null, _excludeCategories_decorators, { kind: "accessor", name: "excludeCategories", static: false, private: false, access: { has: obj => "excludeCategories" in obj, get: obj => obj.excludeCategories, set: (obj, value) => { obj.excludeCategories = value; } }, metadata: _metadata }, _excludeCategories_initializers, _excludeCategories_extraInitializers); __esDecorate(this, null, _defaultCategory_decorators, { kind: "accessor", name: "defaultCategory", static: false, private: false, access: { has: obj => "defaultCategory" in obj, get: obj => obj.defaultCategory, set: (obj, value) => { obj.defaultCategory = value; } }, metadata: _metadata }, _defaultCategory_initializers, _defaultCategory_extraInitializers); if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); } #excludeTags_accessor_storage = __runInitializers(this, _excludeTags_initializers, void 0); get excludeTags() { return this.#excludeTags_accessor_storage; } set excludeTags(value) { this.#excludeTags_accessor_storage = value; } #cascadedModifierTags_accessor_storage = (__runInitializers(this, _excludeTags_extraInitializers), __runInitializers(this, _cascadedModifierTags_initializers, void 0)); get cascadedModifierTags() { return this.#cascadedModifierTags_accessor_storage; } set cascadedModifierTags(value) { this.#cascadedModifierTags_accessor_storage = value; } #excludeInternal_accessor_storage = (__runInitializers(this, _cascadedModifierTags_extraInitializers), __runInitializers(this, _excludeInternal_initializers, void 0)); get excludeInternal() { return this.#excludeInternal_accessor_storage; } set excludeInternal(value) { this.#excludeInternal_accessor_storage = value; } #excludePrivate_accessor_storage = (__runInitializers(this, _excludeInternal_extraInitializers), __runInitializers(this, _excludePrivate_initializers, void 0)); get excludePrivate() { return this.#excludePrivate_accessor_storage; } set excludePrivate(value) { this.#excludePrivate_accessor_storage = value; } #excludeProtected_accessor_storage = (__runInitializers(this, _excludePrivate_extraInitializers), __runInitializers(this, _excludeProtected_initializers, void 0)); get excludeProtected() { return this.#excludeProtected_accessor_storage; } set excludeProtected(value) { this.#excludeProtected_accessor_storage = value; } #excludeNotDocumented_accessor_storage = (__runInitializers(this, _excludeProtected_extraInitializers), __runInitializers(this, _excludeNotDocumented_initializers, void 0)); get excludeNotDocumented() { return this.#excludeNotDocumented_accessor_storage; } set excludeNotDocumented(value) { this.#excludeNotDocumented_accessor_storage = value; } #excludeCategories_accessor_storage = (__runInitializers(this, _excludeNotDocumented_extraInitializers), __runInitializers(this, _excludeCategories_initializers, void 0)); get excludeCategories() { return this.#excludeCategories_accessor_storage; } set excludeCategories(value) { this.#excludeCategories_accessor_storage = value; } #defaultCategory_accessor_storage = (__runInitializers(this, _excludeCategories_extraInitializers), __runInitializers(this, _defaultCategory_initializers, void 0)); get defaultCategory() { return this.#defaultCategory_accessor_storage; } set defaultCategory(value) { this.#defaultCategory_accessor_storage = value; } _excludeKinds = __runInitializers(this, _defaultCategory_extraInitializers); get excludeNotDocumentedKinds() { this._excludeKinds ??= this.application.options .getValue("excludeNotDocumentedKinds") .reduce((a, b) => a | ReflectionKind[b], 0); return this._excludeKinds; } constructor(owner) { super(owner); this.owner.on(ConverterEvents.CREATE_DECLARATION, this.onDeclaration.bind(this)); this.owner.on(ConverterEvents.CREATE_SIGNATURE, this.onDeclaration.bind(this)); this.owner.on(ConverterEvents.CREATE_TYPE_PARAMETER, this.onCreateTypeParameter.bind(this)); this.owner.on(ConverterEvents.RESOLVE_BEGIN, this.onBeginResolve.bind(this)); this.owner.on(ConverterEvents.RESOLVE, this.onResolve.bind(this)); this.owner.on(ConverterEvents.END, () => { this._excludeKinds = undefined; }); } /** * Apply all comment tag modifiers to the given reflection. * * @param reflection The reflection the modifiers should be applied to. * @param comment The comment that should be searched for modifiers. */ applyModifiers(reflection, comment) { if (reflection.kindOf(ReflectionKind.SomeModule)) { comment.removeModifier("@namespace"); } if (reflection.kindOf(ReflectionKind.Interface)) { comment.removeModifier("@interface"); } if (comment.hasModifier("@abstract")) { if (reflection.kindOf(ReflectionKind.SomeSignature)) { reflection.parent.setFlag(ReflectionFlag.Abstract); } else { reflection.setFlag(ReflectionFlag.Abstract); } comment.removeModifier("@abstract"); } if (comment.hasModifier("@private")) { reflection.setFlag(ReflectionFlag.Private); if (reflection.kindOf(ReflectionKind.CallSignature)) { reflection.parent?.setFlag(ReflectionFlag.Private); } comment.removeModifier("@private"); } if (comment.hasModifier("@protected")) { reflection.setFlag(ReflectionFlag.Protected); if (reflection.kindOf(ReflectionKind.CallSignature)) { reflection.parent?.setFlag(ReflectionFlag.Protected); } comment.removeModifier("@protected"); } if (comment.hasModifier("@public")) { reflection.setFlag(ReflectionFlag.Public); if (reflection.kindOf(ReflectionKind.CallSignature)) { reflection.parent?.setFlag(ReflectionFlag.Public); } comment.removeModifier("@public"); } if (comment.hasModifier("@readonly")) { const target = reflection.kindOf(ReflectionKind.GetSignature) ? reflection.parent : reflection; target.setFlag(ReflectionFlag.Readonly); comment.removeModifier("@readonly"); } if (comment.hasModifier("@event") || comment.hasModifier("@eventProperty")) { comment.blockTags.push(new CommentTag("@group", [{ kind: "text", text: "Events" }])); comment.removeModifier("@event"); comment.removeModifier("@eventProperty"); } if (reflection.kindOf(ReflectionKind.Project | ReflectionKind.SomeModule)) { comment.removeTags("@module"); comment.removeModifier("@packageDocumentation"); } } /** * Triggered when the converter has created a type parameter reflection. * * @param context The context object describing the current state the converter is in. * @param reflection The reflection that is currently processed. */ onCreateTypeParameter(_context, reflection) { const comment = reflection.parent?.comment; if (comment) { let tag = comment.getIdentifiedTag(reflection.name, "@typeParam"); if (!tag) { tag = comment.getIdentifiedTag(reflection.name, "@template"); } if (!tag) { tag = comment.getIdentifiedTag(`<${reflection.name}>`, "@param"); } if (!tag) { tag = comment.getIdentifiedTag(reflection.name, "@param"); } if (tag) { reflection.comment = new Comment(tag.content); reflection.comment.sourcePath = comment.sourcePath; removeIfPresent(comment.blockTags, tag); } } } /** * Triggered when the converter has created a declaration or signature reflection. * * Invokes the comment parser. * * @param context The context object describing the current state the converter is in. * @param reflection The reflection that is currently processed. * @param node The node that is currently processed if available. */ onDeclaration(_context, reflection) { this.cascadeModifiers(reflection); const comment = reflection.comment; if (!comment) return; if (reflection.kindOf(ReflectionKind.SomeModule)) { const tag = comment.getTag("@module"); if (tag) { // If no name is specified, this is a flag to mark a comment as a module comment // and should not result in a reflection rename. const newName = Comment.combineDisplayParts(tag.content).trim(); if (newName.length && !newName.includes("\n")) { reflection.name = newName; } removeIfPresent(comment.blockTags, tag); } } this.applyModifiers(reflection, comment); this.removeExcludedTags(comment); } /** * Triggered when the converter begins resolving a project. * * @param context The context object describing the current state the converter is in. */ onBeginResolve(context) { if (context.project.comment) { this.applyModifiers(context.project, context.project.comment); this.removeExcludedTags(context.project.comment); } const project = context.project; const reflections = Object.values(project.reflections); // Remove hidden reflections const hidden = new Set(); for (const ref of reflections) { if (ref.kindOf(ReflectionKind.Accessor) && ref.flags.isReadonly) { const decl = ref; if (decl.setSignature) { hidden.add(decl.setSignature); } // Clear flag set by @readonly since it shouldn't be rendered. ref.setFlag(ReflectionFlag.Readonly, false); } if (this.isHidden(ref)) { hidden.add(ref); } } hidden.forEach((reflection) => project.removeReflection(reflection)); // remove functions with empty signatures after their signatures have been removed const [allRemoved, someRemoved] = partition(unique(filterMap(hidden, (reflection) => reflection.parent?.kindOf(ReflectionKind.SignatureContainer) ? reflection.parent : void 0)), (method) => method.getNonIndexSignatures().length === 0); allRemoved.forEach((reflection) => { project.removeReflection(reflection); }); someRemoved.forEach((reflection) => { reflection.sources = reflection .getNonIndexSignatures() .flatMap((s) => s.sources ?? []); }); } /** * Triggered when the converter resolves a reflection. * * Cleans up comment tags related to signatures like `@param` or `@returns` * and moves their data to the corresponding parameter reflections. * * This hook also copies over the comment of function implementations to their * signatures. * * @param context The context object describing the current state the converter is in. * @param reflection The reflection that is currently resolved. */ onResolve(context, reflection) { if (reflection.comment) { if (reflection.comment.label && !/[A-Z_][A-Z0-9_]/.test(reflection.comment.label)) { context.logger.warn(context.i18n.label_0_for_1_cannot_be_referenced(reflection.comment.label, reflection.getFriendlyFullName())); } for (const group of MUTUALLY_EXCLUSIVE_MODIFIERS) { const intersect = setIntersection(group, reflection.comment.modifierTags); if (intersect.size > 1) { const [a, b] = intersect; context.logger.warn(context.i18n.modifier_tag_0_is_mutually_exclusive_with_1_in_comment_for_2(a, b, reflection.getFriendlyFullName())); } } mergeSeeTags(reflection.comment); movePropertyTags(reflection.comment, reflection); // Unlike other modifiers, this one has to wait until resolution to be removed // as it needs to remain present so that it can be checked when `@hidden` tags are // being processed. if (reflection.kindOf(ReflectionKind.Class)) { reflection.comment.removeModifier("@hideconstructor"); } } if ((reflection instanceof DeclarationReflection || reflection instanceof ParameterReflection) && reflection.comment) { let sigs = []; if (reflection.type instanceof ReflectionType) { sigs = reflection.type.declaration.getNonIndexSignatures(); } else if (reflection instanceof DeclarationReflection) { sigs = reflection.getNonIndexSignatures(); } // For variables and properties, the symbol might own the comment but we might also // have @param and @returns comments for an owned signature. Only do this if there is // exactly one signature as otherwise we have no hope of doing validation right. if (sigs.length === 1 && !sigs[0].comment) { this.moveSignatureParamComments(sigs[0], reflection.comment); const returnsTag = reflection.comment.getTag("@returns"); if (returnsTag) { sigs[0].comment = new Comment(); sigs[0].comment.sourcePath = reflection.comment.sourcePath; sigs[0].comment.blockTags.push(returnsTag); reflection.comment.removeTags("@returns"); } } // Any cascaded tags will show up twice, once on this and once on our signatures // This is completely redundant, so remove them from the wrapping function. if (sigs.length && reflection.type?.type !== "reflection") { for (const mod of this.cascadedModifierTags) { reflection.comment.modifierTags.delete(mod); } } } if (reflection instanceof SignatureReflection) { this.moveSignatureParamComments(reflection); } } moveSignatureParamComments(signature, comment = signature.comment) { if (!comment) return; signature.parameters?.forEach((parameter, index) => { if (parameter.name === "__namedParameters") { const commentParams = comment.blockTags.filter((tag) => tag.tag === "@param" && !tag.name?.includes(".")); if (signature.parameters?.length === commentParams.length && commentParams[index].name) { parameter.name = commentParams[index].name; } } const tag = comment.getIdentifiedTag(parameter.name, "@param"); if (tag) { parameter.comment = new Comment(Comment.cloneDisplayParts(tag.content)); parameter.comment.sourcePath = comment.sourcePath; } }); for (const parameter of signature.typeParameters || []) { const tag = comment.getIdentifiedTag(parameter.name, "@typeParam") || comment.getIdentifiedTag(parameter.name, "@template") || comment.getIdentifiedTag(`<${parameter.name}>`, "@param"); if (tag) { parameter.comment = new Comment(Comment.cloneDisplayParts(tag.content)); parameter.comment.sourcePath = comment.sourcePath; } } this.validateParamTags(signature, comment, signature.parameters || []); comment.removeTags("@param"); comment.removeTags("@typeParam"); comment.removeTags("@template"); } removeExcludedTags(comment) { for (const tag of NEVER_RENDERED) { comment.removeTags(tag); comment.removeModifier(tag); } for (const tag of this.excludeTags) { comment.removeTags(tag); comment.removeModifier(tag); } } cascadeModifiers(reflection) { const parentComment = reflection.parent?.comment; if (!parentComment || reflection.kindOf(ReflectionKind.TypeLiteral)) { return; } const childMods = reflection.comment?.modifierTags ?? new Set(); for (const mod of this.cascadedModifierTags) { if (parentComment.hasModifier(mod)) { const exclusiveSet = MUTUALLY_EXCLUSIVE_MODIFIERS.find((tags) => tags.has(mod)); if (!exclusiveSet || Array.from(exclusiveSet).every((tag) => !childMods.has(tag))) { reflection.comment ||= new Comment(); reflection.comment.modifierTags.add(mod); } } } } /** * Determines whether or not a reflection has been hidden * * @param reflection Reflection to check if hidden */ isHidden(reflection) { const comment = reflection.comment; if (reflection.flags.hasFlag(ReflectionFlag.Private) && this.excludePrivate) { return true; } if (reflection.flags.hasFlag(ReflectionFlag.Protected) && this.excludeProtected) { return true; } if (this.excludedByCategory(reflection)) { return true; } if (reflection.kindOf(ReflectionKind.ConstructorSignature | ReflectionKind.Constructor)) { if (comment?.hasModifier("@hideconstructor")) return true; const cls = reflection.parent?.kindOf(ReflectionKind.Class) ? reflection.parent : reflection.parent?.parent?.kindOf(ReflectionKind.Class) ? reflection.parent.parent : undefined; if (cls?.comment?.hasModifier("@hideconstructor")) { return true; } } if (!comment) { // We haven't moved comments from the parent for signatures without a direct // comment, so don't exclude those due to not being documented. if (reflection.kindOf(ReflectionKind.CallSignature | ReflectionKind.ConstructorSignature) && reflection.parent?.comment) { return false; } if (this.excludeNotDocumented) { // Don't let excludeNotDocumented remove parameters. if (!(reflection instanceof DeclarationReflection) && !(reflection instanceof SignatureReflection)) { return false; } if (!reflection.kindOf(this.excludeNotDocumentedKinds)) { return false; } // excludeNotDocumented should hide a module only if it has no visible children if (reflection.kindOf(ReflectionKind.SomeModule)) { if (!reflection.children) { return true; } return reflection.children.every((child) => this.isHidden(child)); } // signature containers should only be hidden if all their signatures are hidden if (reflection.kindOf(ReflectionKind.SignatureContainer)) { return reflection .getAllSignatures() .every((child) => this.isHidden(child)); } // excludeNotDocumented should never hide parts of "type" reflections return inTypeLiteral(reflection) === false; } return false; } const isHidden = comment.hasModifier("@hidden") || comment.hasModifier("@ignore") || (comment.hasModifier("@internal") && this.excludeInternal); if (!isHidden && reflection.kindOf(ReflectionKind.ContainsCallSignatures)) { return reflection .getNonIndexSignatures() .every((sig) => this.isHidden(sig)); } return isHidden; } excludedByCategory(reflection) { const excludeCategories = this.excludeCategories; let target; if (reflection instanceof DeclarationReflection) { target = reflection; } else if (reflection instanceof SignatureReflection) { target = reflection.parent; } if (!target || !excludeCategories.length) return false; const categories = CategoryPlugin.getCategories(target); if (categories.size === 0) { categories.add(this.defaultCategory); } return excludeCategories.some((cat) => categories.has(cat)); } validateParamTags(signature, comment, params) { const paramTags = comment.blockTags.filter((tag) => tag.tag === "@param"); removeIf(paramTags, (tag) => params.some((param) => param.name === tag.name)); moveNestedParamTags(/* in-out */ paramTags, params, comment.sourcePath); if (!comment.inheritedFromParentDeclaration) { for (const tag of paramTags) { this.application.logger.warn(this.application.i18n.signature_0_has_unused_param_with_name_1(signature.getFriendlyFullName(), tag.name ?? "(missing)")); } } } }; })(); export { CommentPlugin }; function inTypeLiteral(refl) { while (refl) { if (refl.kind === ReflectionKind.TypeLiteral) { return true; } refl = refl.parent; } return false; } function validHighlightedName(ref, name) { const refl = ref.reflection; // Assume external types are documented properly if (!refl) return true; // If it is a direct child, it is valid. if (refl.getChildByName([name])) return true; // Or if it is the child of the referenced reflection's type if (refl.isDeclaration() && refl.type?.type === "reflection") { if (refl.type.declaration.getChildByName([name])) { return true; } } return false; } // Moves tags like `@param foo.bar docs for bar` into the `bar` property of the `foo` parameter. function moveNestedParamTags( /* in-out */ paramTags, parameters, sourcePath) { const used = new Set(); for (const param of parameters) { const visitor = { reflection(target) { const tags = paramTags.filter((t) => t.name?.startsWith(`${param.name}.`)); for (const tag of tags) { const path = tag.name.split("."); path.shift(); const child = target.declaration.getChildOrTypePropertyByName(path); if (child && !child.comment) { child.comment = new Comment(Comment.cloneDisplayParts(tag.content)); child.comment.sourcePath = sourcePath; used.add(paramTags.indexOf(tag)); } } }, // #1876, also do this for unions/intersections. union(u) { u.types.forEach((t) => t.visit(visitor)); }, intersection(i) { i.types.forEach((t) => t.visit(visitor)); }, // #2147, support highlighting parts of a referenced type reference(ref) { for (let i = 0; i < paramTags.length; ++i) { const tag = paramTags[i]; if (tag.name?.startsWith(`${param.name}.`)) { const childName = tag.name.substring(param.name.length + 1); if (!validHighlightedName(ref, childName)) { continue; } ref.highlightedProperties ??= new Map(); ref.highlightedProperties.set(childName, paramTags[i].content); used.add(i); } } }, }; param.type?.visit(visitor); } const toRemove = Array.from(used) .sort((a, b) => a - b) .reverse(); for (const index of toRemove) { paramTags.splice(index, 1); } } function movePropertyTags(comment, container) { const propTags = comment.blockTags.filter((tag) => tag.tag === "@prop" || tag.tag === "@property"); comment.removeTags("@prop"); comment.removeTags("@property"); for (const prop of propTags) { if (!prop.name) continue; const child = container.getChildByName(prop.name); if (child) { child.comment = new Comment(Comment.cloneDisplayParts(prop.content)); child.comment.sourcePath = comment.sourcePath; if (child instanceof DeclarationReflection && child.signatures) { for (const sig of child.signatures) { sig.comment = new Comment(Comment.cloneDisplayParts(prop.content)); sig.comment.sourcePath = comment.sourcePath; } } } } } function mergeSeeTags(comment) { const see = comment.getTags("@see"); if (see.length < 2) return; const index = comment.blockTags.indexOf(see[0]); comment.removeTags("@see"); see[0].content = see.flatMap((part) => [ { kind: "text", text: " - " }, ...part.content, { kind: "text", text: "\n" }, ]); comment.blockTags.splice(index, 0, see[0]); }