UNPKG

prettier-plugin-jsdoc

Version:

Prettier plugin for format comment blocks and convert to standard Match with Visual studio and other IDE which support jsdoc and comments as markdown.

243 lines (242 loc) 9.04 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.findPluginByParser = exports.formatCode = exports.findTokenIndex = exports.detectEndOfLine = exports.capitalizer = exports.addStarsToTheBeginningOfTheLines = exports.formatType = exports.convertToModernType = void 0; const prettier_1 = require("prettier"); const binary_search_bounds_1 = __importDefault(require("binary-search-bounds")); function convertToModernType(oldType) { return withoutStrings(oldType, (type) => { type = type.trim(); // JSDoc supports generics of the form `Foo.<Arg1, Arg2>` type = type.replace(/\.</g, "<"); // JSDoc supports `*` to match any type type = type.replace(/\*/g, " any "); // JSDoc supports `?` (prefix or suffix) to make a type nullable // This is only a limited approximation because the full solution requires // a full TS parser. type = type .replace(/^\?\s*(\w+)$/, "$1 | null") .replace(/^(\w+)\s*\?$/, "$1 | null"); // convert `Array<Foo>` to `Foo[]` let changed = true; while (changed) { changed = false; type = type.replace(/(^|[^$\w\xA0-\uFFFF])Array\s*<((?:[^<>=]|=>|=(?!>)|<(?:[^<>=]|=>|=(?!>))+>)+)>/g, (_, prefix, inner) => { changed = true; return `${prefix}(${inner})[]`; }); } return type; }); } exports.convertToModernType = convertToModernType; /** * Given a valid TS type expression, this will replace all string literals in * the type with unique identifiers. The modified type expression will be passed * to the given map function. The unique identifiers in the output if the map * function will then be replaced with the original string literals. * * This allows the map function to do type transformations without worrying * about string literals. * * @param type * @param mapFn */ function withoutStrings(type, mapFn) { const strings = []; let modifiedType = type.replace( // copied from Prism's C-like language that is used to tokenize JS strings // https://github.com/PrismJS/prism/blob/266cc7002e54dae674817ab06a02c2c15ed64a6f/components/prism-clike.js#L15 /(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/g, (m) => { strings.push(m); // the pattern of the unique identifiers // let's hope that nobody uses an identifier like this in real code return `String$${strings.length - 1}$`; }); if (modifiedType.includes("`")) { // We are current unable to correct handle template literal types. return type; } modifiedType = mapFn(modifiedType); return modifiedType.replace(/String\$(\d+)\$/g, (_, index) => strings[index]); } function formatType(type, options) { try { const TYPE_START = "type name = "; let pretty = type; // Rest parameter types start with "...". This is supported by TS and JSDoc // but it's implemented in a weird way in TS. TS will only acknowledge the // "..." if the function parameter is a rest parameter. In this case, TS // will interpret `...T` as `T[]`. But we can't just convert "..." to arrays // because of @callback types. In @callback types `...T` and `T[]` are not // equivalent, so we have to support "..." as is. // // This formatting itself is rather simple. If `...T` is detected, it will // be replaced with `T[]` and formatted. At the end, the outer array will // be removed and "..." will be added again. // // As a consequence, union types will get an additional pair of parentheses // (e.g. `...A|B` -> `...(A|B)`). This is technically unnecessary but it // makes the operator precedence very clear. // // https://www.typescriptlang.org/docs/handbook/functions.html#rest-parameters let rest = false; if (pretty.startsWith("...")) { rest = true; pretty = `(${pretty.slice(3)})[]`; } pretty = prettier_1.format(`${TYPE_START}${pretty}`, { ...options, parser: "typescript", plugins: [], filepath: "file.ts", }); pretty = pretty.slice(TYPE_START.length); pretty = pretty.replace(/[;\n]*$/g, ""); if (rest) { pretty = "..." + pretty.replace(/\[\s*\]$/, ""); } return pretty; } catch (error) { // console.log("jsdoc-parser", error); return type; } } exports.formatType = formatType; function addStarsToTheBeginningOfTheLines(comment, options) { if (options.jsdocSingleLineComment && numberOfAStringInString(comment.trim(), "\n") === 0) { return `* ${comment.trim()} `; } return `*${comment.replace(/(\n(?!$))/g, "\n * ")}\n `; } exports.addStarsToTheBeginningOfTheLines = addStarsToTheBeginningOfTheLines; function numberOfAStringInString(string, search) { return (string.match(new RegExp(search, "g")) || []).length; } // capitalize if needed function capitalizer(str) { if (!str) { return str; } if (str.match(/^https?:\/\//i)) { return str; } if (str.startsWith("- ")) { return str.slice(0, 2) + capitalizer(str.slice(2)); } return str[0].toUpperCase() + str.slice(1); } exports.capitalizer = capitalizer; /** * Detects the line ends of the given text. * * If multiple line ends are used, the most common one will be returned. * * If the given text is a single line, "lf" will be returned. * * @param text */ function detectEndOfLine(text) { const counter = { "\r": 0, "\r\n": 0, "\n": 0, }; const lineEndPattern = /\r\n?|\n/g; let m; while ((m = lineEndPattern.exec(text))) { counter[m[0]]++; } const cr = counter["\r"]; const crlf = counter["\r\n"]; const lf = counter["\n"]; const max = Math.max(cr, crlf, lf); if (lf === max) { return "lf"; } else if (crlf === max) { return "crlf"; } else { return "cr"; } } exports.detectEndOfLine = detectEndOfLine; /** * Returns the index of a token within the given token array. * * This method uses binary search using the token location. * * @param tokens * @param token */ function findTokenIndex(tokens, token) { return binary_search_bounds_1.default.eq(tokens, token, (a, b) => { if (a.loc.start.line === b.loc.start.line) { return a.loc.start.column - b.loc.start.column; } else { return a.loc.start.line - b.loc.start.line; } }); } exports.findTokenIndex = findTokenIndex; function formatCode(result, beginningSpace, options) { const { printWidth, jsdocKeepUnParseAbleExampleIndent } = options; if (result .split("\n") .slice(1) .every((v) => !v.trim() || v.startsWith(beginningSpace))) { result = result.replace(new RegExp(`\n${beginningSpace.replace(/[\t]/g, "[\\t]")}`, "g"), "\n"); } try { let formattedExample = ""; const examplePrintWith = printWidth - 4; // If example is a json if (result.trim().startsWith("{")) { formattedExample = prettier_1.format(result || "", { ...options, parser: "json", printWidth: examplePrintWith, }); } else { formattedExample = prettier_1.format(result || "", { ...options, printWidth: examplePrintWith, }); } result = formattedExample.replace(/(^|\n)/g, `\n${beginningSpace}`); // Add spaces to start of lines } catch (err) { result = `\n${result .split("\n") .map((l) => `${beginningSpace}${jsdocKeepUnParseAbleExampleIndent ? l : l.trim()}`) .join("\n")}\n`; } return result; } exports.formatCode = formatCode; const findPluginByParser = (parserName, options) => { var _a, _b; const tsPlugin = options.plugins.find((plugin) => { return (typeof plugin === "object" && // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore plugin.name && plugin.parsers && // eslint-disable-next-line no-prototype-builtins plugin.parsers.hasOwnProperty(parserName)); }); return !tsPlugin || // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore tsPlugin.name === "prettier-plugin-jsdoc" || ((_a = tsPlugin.parsers) === null || _a === void 0 ? void 0 : _a.hasOwnProperty("jsdoc-parser")) ? undefined : (_b = tsPlugin.parsers) === null || _b === void 0 ? void 0 : _b[parserName]; }; exports.findPluginByParser = findPluginByParser;