prettier-plugin-jsdoc
Version:
A Prettier plugin to format JSDoc comments.
250 lines • 10.4 kB
JavaScript
import { format } from "prettier";
import { DESCRIPTION, EXAMPLE, TODO } from "./tags.js";
import { capitalizer, formatCode } from "./utils.js";
import { TAGS_PEV_FORMATE_DESCRIPTION } from "./roles.js";
import { fromMarkdown } from "mdast-util-from-markdown";
const TABLE = "2@^5!~#sdE!_TABLE";
const parserSynonyms = (lang) => {
switch (lang) {
case "js":
case "javascript":
case "jsx":
return ["babel", "babel-flow", "vue"];
case "ts":
case "typescript":
case "tsx":
return ["typescript", "babel-ts", "angular"];
case "json":
case "css":
return ["css"];
case "less":
return ["less"];
case "scss":
return ["scss"];
case "html":
return ["html"];
case "yaml":
return ["yaml"];
default:
return ["babel"];
}
};
function descriptionEndLine({ tag, isEndTag, }) {
if ([DESCRIPTION, EXAMPLE, TODO].includes(tag) && !isEndTag) {
return "\n";
}
return "";
}
async function formatDescription(tag, text, options, formatOptions) {
if (!text)
return text;
const { printWidth } = options;
const { tagStringLength = 0, beginningSpace } = formatOptions;
text = text.replace(/^(\d+)[-][\s|]+/g, "$1. ");
text = text.replace(/\n+(\s*\d+)[-][\s]+/g, "\n$1. ");
const fencedCodeBlocks = text.matchAll(/```\S*?\n[\s\S]+?```/gm);
const indentedCodeBlocks = text.matchAll(/^\r?\n^(?:(?:(?:[ ]{4}|\t).*(?:\r?\n|$))+)/gm);
const allCodeBlocks = [...fencedCodeBlocks, ...indentedCodeBlocks];
const tables = [];
text = text.replace(/((\n|^)\|[\s\S]*?)((\n[^|])|$)/g, (code, _1, _2, _3, _, offs) => {
for (const block of allCodeBlocks) {
if (block.index !== undefined &&
block.index <= offs + 1 &&
offs + code.length + 1 <= block.index + block[0].length) {
return code;
}
}
code = _3 ? code.slice(0, -1) : code;
tables.push(code);
return `\n\n${TABLE}\n\n${_3 ? _3.slice(1) : ""}`;
});
if (options.jsdocCapitalizeDescription &&
!TAGS_PEV_FORMATE_DESCRIPTION.includes(tag)) {
text = capitalizer(text);
}
text = `${tagStringLength ? `${"!".repeat(tagStringLength - 1)}?` : ""}${text.startsWith("```") ? "\n" : ""}${text}`;
let tableIndex = 0;
text = text.replace(new RegExp("\\n" + `[\u0020]{${beginningSpace.length}}`, "g"), "\n");
const rootAst = fromMarkdown(text);
async function stringifyASTWithoutChildren(mdAst, intention, parent) {
if (mdAst.type === "inlineCode") {
return `\`${mdAst.value}\``;
}
if (mdAst.type === "code") {
let result = mdAst.value || "";
let _intention = intention;
if (result) {
if (mdAst.lang) {
const supportParsers = parserSynonyms(mdAst.lang.toLowerCase());
const parser = supportParsers?.includes(options.parser)
? options.parser
: supportParsers?.[0] || mdAst.lang;
result = await formatCode(result, intention, {
...options,
parser,
jsdocKeepUnParseAbleExampleIndent: true,
});
}
else if (options.jsdocPreferCodeFences || false) {
result = await formatCode(result, _intention, {
...options,
jsdocKeepUnParseAbleExampleIndent: true,
});
}
else {
_intention = intention + " ".repeat(4);
result = await formatCode(result, _intention, {
...options,
jsdocKeepUnParseAbleExampleIndent: true,
});
}
}
const addFence = options.jsdocPreferCodeFences || !!mdAst.lang;
result = addFence ? result : result.trimEnd();
return result
? addFence
? `\n\n${_intention}\`\`\`${mdAst.lang || ""}${result}\`\`\``
: `\n${result}`
: "";
}
if (mdAst.value === TABLE) {
if (parent) {
parent.costumeType = TABLE;
}
if (tables.length > 0) {
let result = tables?.[tableIndex] || "";
tableIndex++;
if (result) {
result = (await format(result, {
...options,
parser: "markdown",
})).trim();
}
return `${result
? `\n\n${intention}${result.split("\n").join(`\n${intention}`)}`
: mdAst.value}`;
}
}
if (mdAst.type === "break") {
return `\\\n`;
}
return (mdAst.value ||
mdAst.title ||
mdAst.alt ||
"");
}
async function stringyfy(mdAst, intention, parent) {
if (!Array.isArray(mdAst.children)) {
return stringifyASTWithoutChildren(mdAst, intention, parent);
}
return (await Promise.all(mdAst.children.map(async (ast, index) => {
switch (ast.type) {
case "listItem": {
let _listCount = `\n${intention}- `;
if (typeof mdAst.start === "number") {
const count = index + (mdAst.start ?? 1);
_listCount = `\n${intention}${count}. `;
}
const _intention = intention + " ".repeat(_listCount.length - 1);
const result = (await stringyfy(ast, _intention, mdAst)).trim();
return `${_listCount}${result}`;
}
case "list": {
let end = "";
if (tag !== DESCRIPTION &&
mdAst.type === "root" &&
index === mdAst.children.length - 1) {
end = "\n";
}
return `\n${await stringyfy(ast, intention, mdAst)}${end}`;
}
case "paragraph": {
const paragraph = await stringyfy(ast, intention, parent);
if (ast.costumeType === TABLE) {
return paragraph;
}
return `\n\n${paragraph
.split("\\\n")
.map((_paragraph) => {
const links = [];
_paragraph = _paragraph.replace(/{@(link|linkcode|linkplain)[\s](([^{}])*)}/g, (_, tag, link) => {
links.push(link);
return `{@${tag}${"_".repeat(link.length)}}`;
});
_paragraph = _paragraph.replace(/\s+/g, " ");
if (options.jsdocCapitalizeDescription &&
!TAGS_PEV_FORMATE_DESCRIPTION.includes(tag))
_paragraph = capitalizer(_paragraph);
if (options.jsdocDescriptionWithDot)
_paragraph = _paragraph.replace(/([\w\p{L}])$/u, "$1.");
let result = breakDescriptionToLines(_paragraph, printWidth, intention);
result = result.replace(/{@(link|linkcode|linkplain)([_]+)}/g, (original, tag, underline) => {
const link = links[0];
if (link.length === underline.length) {
links.shift();
return `{@${tag} ${link}}`;
}
return original;
});
return result;
})
.join("\\\n")}`;
}
case "strong": {
return `**${await stringyfy(ast, intention, mdAst)}**`;
}
case "emphasis": {
return `_${await stringyfy(ast, intention, mdAst)}_`;
}
case "heading": {
return `\n\n${intention}${"#".repeat(ast.depth)} ${await stringyfy(ast, intention, mdAst)}`;
}
case "link":
case "image": {
return `[${await stringyfy(ast, intention, mdAst)}](${ast.url})`;
}
case "linkReference": {
return `[${await stringyfy(ast, intention, mdAst)}][${ast.label}]`;
}
case "definition": {
return `\n\n[${ast.label}]: ${ast.url}`;
}
case "blockquote": {
const paragraph = await stringyfy(ast, "", mdAst);
return `\n\n> ${paragraph
.trim()
.replace(/(\n+)/g, `$1${intention}> `)}`;
}
}
return stringyfy(ast, intention, mdAst);
}))).join("");
}
let result = await stringyfy(rootAst, beginningSpace, null);
result = result.replace(/^[\s\n]+/g, "");
result = result.replace(/^([!]+\?)/g, "");
return result;
}
function breakDescriptionToLines(desContent, maxWidth, beginningSpace) {
let str = desContent.trim();
if (!str) {
return str;
}
let result = "";
while (str.length > maxWidth) {
let sliceIndex = str.lastIndexOf(" ", str.startsWith("\n") ? maxWidth + 1 : maxWidth);
if (sliceIndex <= beginningSpace.length)
sliceIndex = str.indexOf(" ", beginningSpace.length + 1);
if (sliceIndex === -1)
sliceIndex = str.length;
result += str.substring(0, sliceIndex);
str = str.substring(sliceIndex + 1);
if (str) {
str = `${beginningSpace}${str}`;
str = `\n${str}`;
}
}
result += str;
return `${beginningSpace}${result}`;
}
export { descriptionEndLine, formatDescription };
//# sourceMappingURL=descriptionFormatter.js.map