UNPKG

prettier-plugin-jsdoc

Version:

Prettier plugin for format jsdoc and convert to standard Match with Visual studio and other IDE which support jsdoc.

285 lines (284 loc) 12.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.jsdocParser = void 0; const comment_parser_1 = __importDefault(require("comment-parser")); const prettier_1 = require("prettier"); const type_1 = require("./type"); const tags_1 = require("./tags"); const roles_1 = require("./roles"); const EMPTY_LINE_SIGNATURE = "a2@1093NmY^5!~#sdEKHuhPOK*&baSIGNATURE1"; const NEW_LINE_START_THREE_SPACE_SIGNATURE = "l2@_0^3N)Y^5!~^sd*KHuh+O~*;vlSIGNATURE2"; const NEW_PARAGRAPH_START_THREE_SPACE_SIGNATURE = "l2@_0^s43fb64ds2Huh+O~*;vlSIGNATURE3"; /** * @link https://prettier.io/docs/en/api.html#custom-parser-api} */ function jsdocParser(text, parsers, options) { const babelTs = parsers["babel-ts"]; const ast = babelTs(text); // Options const gap = " ".repeat(options.jsdocSpaces); const { printWidth = 80 } = options; /** * Control order of tags by weights. Smaller value brings tag higher. * * @param {String} tagTitle TODO * @returns {Number} Tag weight */ function getTagOrderWeight(tagTitle) { if (tagTitle === tags_1.DESCRIPTION && !options.jsdocDescriptionTag) { return -1; } const index = options.jsdocTagsOrder.indexOf(tagTitle); return index === -1 ? options.jsdocTagsOrder.indexOf("other") || options.jsdocTagsOrder.length : index; } ast.comments.forEach((comment) => { const { loc: { start: { column }, }, } = comment; // Parse only comment blocks if (comment.type !== "CommentBlock") return; const commentString = `/*${comment.value}*/`; // Check if this comment block is a JSDoc. Based on: // https://github.com/jsdoc/jsdoc/blob/master/packages/jsdoc/plugins/commentsOnly.js if (!commentString.match(/\/\*\*[\s\S]+?\*\//g)) return; const parsed = comment_parser_1.default(commentString, { dotted_names: false, trim: false, })[0]; comment.value = ""; convertCommentDescToDescTag(parsed); let maxTagTitleLength = 0; let maxTagTypeNameLength = 0; let maxTagNameLength = 0; parsed.tags // Prepare tags data .map(({ name, description, type, tag, source, optional, default: _default, ...restInfo }) => { tag = tag && tag.trim().toLowerCase(); //@ts-ignore tag = roles_1.TAGS_SYNONYMS[tag] || tag; const isVerticallyAlignAbleTags = roles_1.TAGS_VERTICALLY_ALIGN_ABLE.includes(tag); if (roles_1.TAGS_NAMELESS.includes(tag) && name) { description = `${name} ${description}`; name = ""; } if (type) { type = type_1.convertToModernArray(type); type = type_1.formatType(type, options); if (isVerticallyAlignAbleTags) maxTagTypeNameLength = Math.max(maxTagTypeNameLength, type.length); // Additional operations on name if (name) { // Optional tag name if (optional) { // Figure out if tag type have default value if (_default) { description += ` Default is \`${_default}\``; } name = `[${name}]`; } if (isVerticallyAlignAbleTags) maxTagNameLength = Math.max(maxTagNameLength, name.length); } } if (isVerticallyAlignAbleTags) { maxTagTitleLength = Math.max(maxTagTitleLength, tag.length); } description = formatDescription(tag, description, options.jsdocDescriptionWithDot); return { ...restInfo, name, description, type, tag, source, default: _default, optional, }; }) // Sort tags .sort((a, b) => getTagOrderWeight(a.tag) - getTagOrderWeight(b.tag)) .filter(({ description, tag }) => { if (!description && roles_1.TAGS_DESCRIPTION_NEEDED.includes(tag)) { return false; } return true; }) // Create final jsDoc string .forEach(({ name, description, type, tag }, tagIndex, finalTagsArray) => { let tagTitleGapAdj = 0; let tagTypeGapAdj = 0; let tagNameGapAdj = 0; let descGapAdj = 0; if (options.jsdocVerticalAlignment && roles_1.TAGS_VERTICALLY_ALIGN_ABLE.includes(tag)) { if (tag) tagTitleGapAdj += maxTagTitleLength - tag.length; else if (maxTagTitleLength) descGapAdj += maxTagTitleLength + gap.length; if (type) tagTypeGapAdj += maxTagTypeNameLength - type.length; else if (maxTagTypeNameLength) descGapAdj += maxTagTypeNameLength + gap.length; if (name) tagNameGapAdj += maxTagNameLength - name.length; else if (maxTagNameLength) descGapAdj = maxTagNameLength + gap.length; } let useTagTitle = tag !== tags_1.DESCRIPTION || options.jsdocDescriptionTag; let tagString = "\n"; if (useTagTitle) { try { tagString += `@${tag}${" ".repeat(tagTitleGapAdj)}`; } catch (error) { console.log(error); } } if (type) { tagString += gap + `{${type}}` + " ".repeat(tagTypeGapAdj); } if (name) tagString += `${gap}${name}${" ".repeat(tagNameGapAdj)}`; // Add description (complicated because of text wrap) if (description && tag !== tags_1.EXAMPLE) { if (useTagTitle) tagString += gap + " ".repeat(descGapAdj); if ([tags_1.MEMBEROF, tags_1.SEE].includes(tag)) { // Avoid wrapping tagString += description; } else { // Wrap tag description const beginningSpace = tag === tags_1.DESCRIPTION ? "" : " "; // google style guide space const marginLength = tagString.length; let maxWidth = printWidth - column - 3; // column is location of comment, 3 is ` * ` if (marginLength >= maxWidth) { maxWidth = marginLength; } let resolveDescription = `${tagString}${description}`; tagString = resolveDescription .split(NEW_PARAGRAPH_START_THREE_SPACE_SIGNATURE) .map((newParagraph) => { return newParagraph .split(EMPTY_LINE_SIGNATURE) .map((paragraph) => { paragraph = paragraph[0].toUpperCase() + paragraph.slice(1); // Capitalize return paragraph .split(NEW_LINE_START_THREE_SPACE_SIGNATURE) .map((desContent) => { desContent = desContent.trim(); if (!desContent) { return desContent; } let result = ""; while (desContent.length > maxWidth) { let sliceIndex = desContent.lastIndexOf(" ", maxWidth); if (sliceIndex === -1) sliceIndex = maxWidth; result += desContent.substring(0, sliceIndex); desContent = desContent.substring(sliceIndex + 1); desContent = `\n${beginningSpace}${desContent}`; } result += desContent; return result; }) .join("\n "); }) .join("\n\n"); }) .join("\n\n "); // tagString = tagString.trim(); tagString = tagString ? `\n${tagString}` : tagString; } } // Try to use prettier on @example tag description if (tag === tags_1.EXAMPLE) { try { const formattedExample = prettier_1.format(description || "", options); tagString += formattedExample.replace(/(^|\n)/g, "\n "); tagString = tagString.slice(0, tagString.length - 3); } catch (err) { tagString += "\n"; tagString += description .split("\n") .map((l) => ` ${options.jsdocKeepUnParseAbleExampleIndent ? l : l.trim()}`) .join("\n"); } } // Add empty line after some tags if there is something below tagString += descriptionEndLine({ description: tagString, tag, isEndTag: tagIndex === finalTagsArray.length - 1, }); comment.value += tagString; }); comment.value = addStarsToTheBeginningOfTheLines(comment.value); }); return ast; } exports.jsdocParser = jsdocParser; /** * Trim, make single line with capitalized text. Insert dot if flag for it is * set to true and last character is a word character * * @private * @param {Boolean} insertDot Flag for dot at the end of text */ function formatDescription(tag, text, insertDot) { text = text || ""; text = text.replace(/^[\W]/g, ""); text = text.trim(); if (!roles_1.TAGS_NEED_FORMAT_DESCRIPTION.includes(tag)) { return text; } if (!text) return text; text = text.replace(/\n\n\s\s\s+/g, NEW_PARAGRAPH_START_THREE_SPACE_SIGNATURE); // Add a signature for new paragraph start with three space text = text.replace(/\n\s\s\s+/g, NEW_LINE_START_THREE_SPACE_SIGNATURE); // Add a signature for new line start with three space text = text.replace(/\n\n/g, EMPTY_LINE_SIGNATURE); // Add a signature for empty line and use that later text = text.replace(/\s\s+/g, " "); // Avoid multiple spaces text = text.replace(/\n/g, " "); // Make single line if (insertDot) text = text.replace(/(\w)(?=$)/g, "$1."); // Insert dot if needed text = text[0].toUpperCase() + text.slice(1); // Capitalize return text || ""; } function convertCommentDescToDescTag(parsed) { if (!parsed.description) { return; } const Tag = parsed.tags.find(({ tag }) => tag.toLowerCase() === tags_1.DESCRIPTION); let { description = "" } = Tag || {}; description += parsed.description; if (Tag) { Tag.description = description; } else { parsed.tags.push({ tag: tags_1.DESCRIPTION, description }); } } function descriptionEndLine({ description, tag, isEndTag, }) { if (description.trim().length < 0 || isEndTag) { return ""; } if ([tags_1.DESCRIPTION, tags_1.EXAMPLE, tags_1.TODO].includes(tag)) { return "\n"; } return ""; } function addStarsToTheBeginningOfTheLines(comment) { if (numberOfAStringInString(comment.trim(), "\n") === 0) { return `* ${comment.trim()} `; } return `*${comment.replace(/((?!\n$)\n)/g, "\n * ")}\n `; } function numberOfAStringInString(string, search) { return (string.match(new RegExp(search, "g")) || []).length; }