typedoc
Version:
Create api documentation for TypeScript projects.
518 lines (517 loc) • 22.1 kB
JavaScript
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;
}
}