UNPKG

prettier-plugin-jsdoc

Version:
302 lines 13.5 kB
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; const originalText = text; 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)}}`; }); const applyCapitalization = (text) => { const shouldCapitalize = options.jsdocCapitalizeDescription && !TAGS_PEV_FORMATE_DESCRIPTION.includes(tag); return shouldCapitalize ? capitalizer(text) : text; }; const applyTrailingDot = (text) => { return options.jsdocDescriptionWithDot ? text.replace(/([\w\p{L}])$/u, "$1.") : text; }; const applyStandardFormatting = (text) => { return applyTrailingDot(applyCapitalization(text)); }; const joinWithIndentation = (lines) => { const indentedLines = lines.map((line) => `${intention}${line}`); return indentedLines.join("\n"); }; const applyGreedyWrapping = (text) => { const singleLine = text.replace(/\s+/g, " "); const formatted = applyStandardFormatting(singleLine); return breakDescriptionToLines(formatted, printWidth, intention); }; const isBalanceMode = options.jsdocLineWrappingStyle === "balance"; const originalHasLineBreaks = originalText.includes("\n"); const shouldTryBalanceMode = isBalanceMode && originalHasLineBreaks; let result; if (shouldTryBalanceMode) { const originalLines = originalText .split("\n") .map((line) => line.trim()) .filter((line) => line.length > 0); const effectiveMaxWidth = tag === DESCRIPTION && tagStringLength > 0 ? printWidth - tagStringLength : printWidth - intention.length; const allLinesFit = originalLines.every((line) => line.length <= effectiveMaxWidth); const hasMultipleLines = originalLines.length > 1; if (allLinesFit && hasMultipleLines) { const formattedLines = originalLines.map((line, index) => { const isFirstLine = index === 0; const isLastLine = index === originalLines.length - 1; let formatted = line; if (isFirstLine) { formatted = applyCapitalization(formatted); } if (isLastLine) { formatted = applyTrailingDot(formatted); } return formatted; }); result = joinWithIndentation(formattedLines); } else { result = applyGreedyWrapping(_paragraph); } } else { result = applyGreedyWrapping(_paragraph); } 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