@appium/typedoc-plugin-appium
Version:
TypeDoc plugin for Appium & its extensions
156 lines • 6.73 kB
JavaScript
;
/**
* Utilities to derive comments from various sources
* @module
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractExamples = exports.cloneComment = exports.deriveComment = exports.NAME_EXAMPLE_TAG = void 0;
const lodash_1 = __importDefault(require("lodash"));
const typedoc_1 = require("typedoc");
const types_1 = require("../types");
const finder_1 = require("./finder");
const logger_1 = require("../../logger");
const log = logger_1.fallbackLogger.createChildLogger('comment');
exports.NAME_EXAMPLE_TAG = '@example';
/**
* The beginning of fenced code block looks like this.
*
* @todo Ensure the _end_ of the fenced code block exists
*/
const FENCED_CODE_BLOCK_REGEX = /^\s*```(\w+)?/;
/**
* Tries to figure out a comment for a command
* @param opts Options
* @returns A {@linkcode CommentData} containing a {@linkcode Comment}, if one can be found
* @internal
*/
function deriveComment(opts = {}) {
const { refl, comment, knownMethods } = opts;
const commentFinders = (0, finder_1.getFinders)(refl);
/**
* The result of running thru all of the comment finders. Each value will have a
* {@linkcode CommentSource} corresponding to the finder, and the a `comment` property _if and
* only if_ a {@linkcode Comment} was found.
*/
const rawCommentData = commentFinders.map(({ getter, commentSource }) => ({
comment: getter({ refl, comment, knownBuiltinMethods: knownMethods }),
commentSource,
}));
/**
* The result of filtering out any {@linkcode CommentData} objects which do not have a `comment` property
* _or_ which do but the {@linkcode Comment} property does not have any visible components.
*
* A comment without visible components is defined by TypeDoc as one without a "summary" (or an
* empty summary) and one which has no block tags. Block tags, by definition, display something.
*/
const commentData = rawCommentData.filter(({ comment }) => comment?.hasVisibleComponent());
// the first summary found; this is where precedence comes in to play
const summaryCommentData = commentData.find(({ comment }) => comment.summary.length);
// in this bit we want to pull all block tags from all comments, and see which have content and
// which don't. for each tag name (e.g., `@returns`), prefer the tag with content. if there is
// no such tag, just pick the first one
const allBlockTags = commentData.flatMap(({ comment }) => comment.blockTags);
const tagsByTag = lodash_1.default.groupBy(allBlockTags, 'tag');
const finalBlockTags = [];
for (const tags of Object.values(tagsByTag)) {
if (tags.length === 1) {
finalBlockTags.push(tags[0]);
}
else if (tags.length > 1) {
// prefer a tag with content
const tag = tags.find((t) => t.content.length) ?? tags[0];
finalBlockTags.push(tag);
}
else {
if (refl) {
log.warn('No comment tags found in derived comment for %s "%s". This is a bug', refl.constructor.name, refl.name);
}
}
}
// if we have a summary comment and some block tags, and the block tags are not the same as the
// summary comment's block tags, then create a new comment merged from the summary and all block
// tags.
if (summaryCommentData &&
finalBlockTags.length &&
lodash_1.default.xor(summaryCommentData.comment.blockTags, finalBlockTags).length) {
const comment = cloneComment(summaryCommentData.comment, finalBlockTags);
return { comment, commentSource: types_1.CommentSource.Multiple };
}
// pick the first one found
return lodash_1.default.first(commentData);
}
exports.deriveComment = deriveComment;
/**
* Clones a comment. Mostly. I think.
* @param comment Comment to clone
* @param blockTags Block tags to use; if not provided, the comment's block tags will be used (these
* are also cloned)
* @returns A new comment
*/
function cloneComment(comment, blockTags) {
const newComment = new typedoc_1.Comment(typedoc_1.Comment.cloneDisplayParts(comment.summary), (blockTags ?? comment.blockTags).map((blockTag) => {
const tag = new typedoc_1.CommentTag(blockTag.tag, typedoc_1.Comment.cloneDisplayParts(blockTag.content));
tag.name = blockTag.name;
return tag;
}));
newComment.modifierTags = new Set(comment.modifierTags);
return newComment;
}
exports.cloneComment = cloneComment;
/**
* Type guard to narrow a string to a key of {@linkcode ExampleLanguage}
* @param value anything
* @returns `true` if the value is a valid language
*/
function isValidLang(value) {
return value in types_1.ExampleLanguage;
}
/**
* Finds any `@example` tags within a comment and creates an {@linkcode ExtractedExamples} object
* for them. Creates a new comment with the examples removed, so we can handle them in a
* custom way via our theme.
*
* Note that the same `Comment` can be passed multiple times (if it is displayed in multiple
* modules). Our choices are either to a) clone the comment each time, or b) memoize the function
* in order to avoid a problem wherein the comment has its examples extracted on the first call, but
* subsequent calls will not contain any examples (since they've already been extracted). It may be
* more memory-friendly to memoize, but it may not be the safest thing to do (unclear).
*
* @param comment A comment to extract examples from
* @returns New comment (with examples removed)
*/
const extractExamples = (comment) => {
if (!comment) {
return;
}
comment = cloneComment(comment);
const exampleTags = comment.getTags(exports.NAME_EXAMPLE_TAG);
if (!exampleTags) {
return { comment };
}
const examples = exampleTags.flatMap(({ content }) => content.reduce((examples, { text }) => {
const match = text.match(FENCED_CODE_BLOCK_REGEX);
if (match) {
const matchedLang = match[1] ?? 'js';
const lang = isValidLang(matchedLang) ? matchedLang : 'js';
return [
...examples,
{
lang: types_1.ExampleLanguage[lang],
text,
},
];
}
return examples;
}, []));
if (examples.length) {
comment.removeTags(exports.NAME_EXAMPLE_TAG);
return { examples, comment };
}
return { comment };
};
exports.extractExamples = extractExamples;
//# sourceMappingURL=index.js.map