UNPKG

typedoc

Version:

Create api documentation for TypeScript projects.

518 lines (517 loc) 22.1 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 { assertNever, i18n, NonEnumerable, removeIf } from "#utils"; import { ReflectionSymbolId } from "./ReflectionSymbolId.js"; /** * A model that represents a single TypeDoc comment tag. * * Tags are stored in the {@link Comment.blockTags} property. * @category Comments */ export class CommentTag { /** * The name of this tag, e.g. `@returns`, `@example` */ tag; /** * Some tags, (`@typedef`, `@param`, `@property`, etc.) may have a user defined identifier associated with them. * If this tag is one of those, it will be parsed out and included here. */ name; /** * The actual body text of this tag. */ content; /** * A flag which may be set by plugins to prevent TypeDoc from rendering this tag, if the plugin provides * custom rendering. Note: This flag is **not** serialized, it is expected to be set just before the comment * is rendered. */ skipRendering = false; /** * Create a new CommentTag instance. */ constructor(tag, text) { this.tag = tag; this.content = text; } /** * Checks if this block tag is roughly equal to the other tag. * This isn't exactly equal, but just "roughly equal" by the tag * text. */ similarTo(other) { return (this.tag === other.tag && this.name === other.tag && Comment.combineDisplayParts(this.content) === Comment.combineDisplayParts(other.content)); } clone() { const tag = new CommentTag(this.tag, Comment.cloneDisplayParts(this.content)); if (this.name) { tag.name = this.name; } return tag; } toObject() { return { tag: this.tag, name: this.name, content: Comment.serializeDisplayParts(this.content), }; } fromObject(de, obj) { // tag already set by Comment.fromObject this.name = obj.name; this.content = Comment.deserializeDisplayParts(de, obj.content); } } /** * A model that represents a comment. * * Instances of this model are created by the CommentPlugin. You can retrieve comments * through the {@link DeclarationReflection.comment} property. * @category Comments */ let Comment = (() => { let _sourcePath_decorators; let _sourcePath_initializers = []; let _sourcePath_extraInitializers = []; let _discoveryId_decorators; let _discoveryId_initializers = []; let _discoveryId_extraInitializers = []; let _inheritedFromParentDeclaration_decorators; let _inheritedFromParentDeclaration_initializers = []; let _inheritedFromParentDeclaration_extraInitializers = []; return class Comment { static { const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0; _sourcePath_decorators = [NonEnumerable]; _discoveryId_decorators = [NonEnumerable]; _inheritedFromParentDeclaration_decorators = [NonEnumerable]; __esDecorate(null, null, _sourcePath_decorators, { kind: "field", name: "sourcePath", static: false, private: false, access: { has: obj => "sourcePath" in obj, get: obj => obj.sourcePath, set: (obj, value) => { obj.sourcePath = value; } }, metadata: _metadata }, _sourcePath_initializers, _sourcePath_extraInitializers); __esDecorate(null, null, _discoveryId_decorators, { kind: "field", name: "discoveryId", static: false, private: false, access: { has: obj => "discoveryId" in obj, get: obj => obj.discoveryId, set: (obj, value) => { obj.discoveryId = value; } }, metadata: _metadata }, _discoveryId_initializers, _discoveryId_extraInitializers); __esDecorate(null, null, _inheritedFromParentDeclaration_decorators, { kind: "field", name: "inheritedFromParentDeclaration", static: false, private: false, access: { has: obj => "inheritedFromParentDeclaration" in obj, get: obj => obj.inheritedFromParentDeclaration, set: (obj, value) => { obj.inheritedFromParentDeclaration = value; } }, metadata: _metadata }, _inheritedFromParentDeclaration_initializers, _inheritedFromParentDeclaration_extraInitializers); if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); } /** * Debugging utility for combining parts into a simple string. Not suitable for * rendering, but can be useful in tests. */ static combineDisplayParts(parts) { let result = ""; for (const item of parts || []) { switch (item.kind) { case "text": case "code": case "relative-link": result += item.text; break; case "inline-tag": result += `{${item.tag} ${item.text}}`; break; default: assertNever(item); } } return result; } /** * Helper utility to clone {@link Comment.summary} or {@link CommentTag.content} */ static cloneDisplayParts(parts) { return parts.map((p) => ({ ...p })); } static serializeDisplayParts(parts) { return parts?.map((part) => { switch (part.kind) { case "text": case "code": return { ...part }; case "inline-tag": { let target; if (typeof part.target === "string") { target = part.target; } else if (part.target) { if ("id" in part.target) { target = part.target.id; } else { target = part.target.toObject(); } } return { ...part, target, }; } case "relative-link": { return { ...part, }; } } }); } // Since display parts are plain objects, this lives here static deserializeDisplayParts(de, parts) { const links = []; const files = []; const result = parts.map((part) => { switch (part.kind) { case "text": case "code": return { ...part }; case "inline-tag": { if (typeof part.target === "number") { const part2 = { kind: part.kind, tag: part.tag, text: part.text, target: undefined, tsLinkText: part.tsLinkText, }; links.push([part.target, part2]); return part2; } else if (typeof part.target === "string" || part.target === undefined) { return { kind: "inline-tag", tag: part.tag, text: part.text, target: part.target, tsLinkText: part.tsLinkText, }; } else if (typeof part.target === "object") { return { kind: "inline-tag", tag: part.tag, text: part.text, target: new ReflectionSymbolId(part.target), tsLinkText: part.tsLinkText, }; } else { assertNever(part.target); } break; } case "relative-link": { if (part.target) { const part2 = { kind: "relative-link", text: part.text, target: null, targetAnchor: part.targetAnchor, }; files.push([part.target, part2]); return part2; } return { ...part, target: undefined, targetAnchor: part.targetAnchor, }; } } }); if (links.length || files.length) { de.defer((project) => { for (const [oldFileId, part] of files) { part.target = de.oldFileIdToNewFileId[oldFileId]; } for (const [oldId, part] of links) { part.target = project.getReflectionById(de.oldIdToNewId[oldId] ?? -1); if (!part.target) { de.logger.warn(i18n.serialized_project_referenced_0_not_part_of_project(oldId.toString())); } } }); } return result; } /** * Splits the provided parts into a header (first line, as a string) * and body (remaining lines). If the header line contains inline tags * they will be serialized to a string. */ static splitPartsToHeaderAndBody(parts) { let index = parts.findIndex((part) => { switch (part.kind) { case "text": case "code": return part.text.includes("\n"); case "inline-tag": case "relative-link": return false; } }); if (index === -1) { return { header: Comment.combineDisplayParts(parts), body: [], }; } // Do not split a code block, stop the header at the end of the previous block if (parts[index].kind === "code") { --index; } if (index === -1) { return { header: "", body: Comment.cloneDisplayParts(parts) }; } let header = Comment.combineDisplayParts(parts.slice(0, index)); const split = parts[index].text.indexOf("\n"); let body; if (split === -1) { header += parts[index].text; body = Comment.cloneDisplayParts(parts.slice(index + 1)); } else { header += parts[index].text.substring(0, split); body = Comment.cloneDisplayParts(parts.slice(index)); body[0].text = body[0].text.substring(split + 1); } if (!body[0].text) { body.shift(); } return { header: header.trim(), body }; } /** * The content of the comment which is not associated with a block tag. */ summary; /** * All associated block level tags. */ blockTags = []; /** * All modifier tags present on the comment, e.g. `@alpha`, `@beta`. */ modifierTags = new Set(); /** * Label associated with this reflection, if any (https://tsdoc.org/pages/tags/label/) */ label; /** * Full path to the file where this comment originated from, if any. * This field will not be serialized, so will not be present when handling JSON-revived reflections. * * Note: This field is non-enumerable to make testing comment contents with `deepEqual` easier. */ sourcePath = __runInitializers(this, _sourcePath_initializers, void 0); /** * Internal discovery ID used to prevent symbol comments from * being duplicated on signatures. Only set when the comment was created * from a `ts.CommentRange`. * @internal */ discoveryId = (__runInitializers(this, _sourcePath_extraInitializers), __runInitializers(this, _discoveryId_initializers, void 0)); /** * If the comment was inherited from a different "parent" declaration * (see #2545), then it is desirable to know this as any `@param` tags * which do not apply should not cause warnings. This is not serialized, * and only set when the comment was created from a `ts.CommentRange`. */ inheritedFromParentDeclaration = (__runInitializers(this, _discoveryId_extraInitializers), __runInitializers(this, _inheritedFromParentDeclaration_initializers, void 0)); /** * Creates a new Comment instance. */ constructor(summary = [], blockTags = [], modifierTags = new Set()) { __runInitializers(this, _inheritedFromParentDeclaration_extraInitializers); this.summary = summary; this.blockTags = blockTags; this.modifierTags = modifierTags; extractLabelTag(this); } /** * Gets either the `@summary` tag, or a short version of the comment summary * section for rendering in module/namespace pages. */ getShortSummary(useFirstParagraph) { const tag = this.getTag("@summary"); if (tag) return tag.content; if (!useFirstParagraph) return []; let partsEnd = this.summary.findIndex((part) => { switch (part.kind) { case "text": return part.text.includes("\n\n"); case "code": return part.text.includes("\n"); case "inline-tag": case "relative-link": return false; default: assertNever(part); } }); const foundEnd = partsEnd !== -1; if (partsEnd === -1) { partsEnd = this.summary.length - 1; } const summaryParts = this.summary.slice(0, partsEnd); if (partsEnd !== -1) { const text = this.summary[partsEnd].text; const paragraphEnd = text.indexOf("\n\n"); if (paragraphEnd !== -1) { summaryParts.push({ ...this.summary[partsEnd], text: text.slice(0, paragraphEnd), }); } else if (!foundEnd) { summaryParts.push(this.summary[partsEnd]); } } return summaryParts; } /** * Checks if this comment is roughly equal to the other comment. * This isn't exactly equal, but just "roughly equal" by the comment * text. */ similarTo(other) { if (Comment.combineDisplayParts(this.summary) !== Comment.combineDisplayParts(other.summary)) { return false; } // Ignore modifier tags, as they could cause false negatives // if a cascaded modifier tag is present in one comment but not the other. if (this.blockTags.length !== other.blockTags.length) { return false; } for (let i = 0; i < this.blockTags.length; ++i) { if (!this.blockTags[i].similarTo(other.blockTags[i])) { return false; } } return true; } /** * Create a deep clone of this comment. */ clone() { const comment = new Comment(Comment.cloneDisplayParts(this.summary), this.blockTags.map((tag) => tag.clone()), new Set(this.modifierTags)); comment.discoveryId = this.discoveryId; comment.sourcePath = this.sourcePath; comment.inheritedFromParentDeclaration = this.inheritedFromParentDeclaration; return comment; } /** * Returns true if this comment is completely empty. * @internal */ isEmpty() { return !this.hasVisibleComponent() && this.modifierTags.size === 0; } /** * Has this comment a visible component? * * @returns TRUE when this comment has a visible component. */ hasVisibleComponent() { return (this.summary.some((x) => x.kind !== "text" || x.text !== "") || this.blockTags.length > 0); } /** * Test whether this comment contains a tag with the given name. * * @param tagName The name of the tag to look for. * @returns TRUE when this comment contains a tag with the given name, otherwise FALSE. */ hasModifier(tagName) { return this.modifierTags.has(tagName); } removeModifier(tagName) { this.modifierTags.delete(tagName); } /** * Return the first tag with the given name. * * @param tagName The name of the tag to look for. * @returns The found tag or undefined. */ getTag(tagName) { return this.blockTags.find((tag) => tag.tag === tagName); } /** * Get all tags with the given tag name. */ getTags(tagName) { return this.blockTags.filter((tag) => tag.tag === tagName); } getIdentifiedTag(identifier, tagName) { return this.blockTags.find((tag) => tag.tag === tagName && tag.name === identifier); } /** * Removes all block tags with the given tag name from the comment. * @param tagName */ removeTags(tagName) { removeIf(this.blockTags, (tag) => tag.tag === tagName); } toObject(serializer) { return { summary: Comment.serializeDisplayParts(this.summary), blockTags: serializer.toObjectsOptional(this.blockTags), modifierTags: this.modifierTags.size > 0 ? Array.from(this.modifierTags) : undefined, label: this.label, }; } fromObject(de, obj) { this.summary = Comment.deserializeDisplayParts(de, obj.summary); this.blockTags = obj.blockTags?.map((tagObj) => { const tag = new CommentTag(tagObj.tag, []); de.fromObject(tag, tagObj); return tag; }) || []; this.modifierTags = new Set(obj.modifierTags); this.label = obj.label; } }; })(); export { Comment }; function extractLabelTag(comment) { const index = comment.summary.findIndex((part) => part.kind === "inline-tag" && part.tag === "@label"); if (index !== -1) { comment.label = comment.summary.splice(index, 1)[0].text; } }