UNPKG

eslint-plugin-complete

Version:

An ESLint plugin that contains useful rules.

118 lines (117 loc) 4.9 kB
import { trimPrefix } from "../completeCommon.js"; import { formatText } from "../format.js"; import { getJSDocComments, getTextFromJSDocComment } from "../jsdoc.js"; import { areStringsEqualExcludingTrailingSpaces, createRule, } from "../utils.js"; const EXTRA_NUM_CHARACTERS_TO_FIT_ON_JSDOC_SINGLE_LINE = 4; const DEBUG = false; export const formatJSDocComments = createRule({ name: "format-jsdoc-comments", meta: { type: "layout", docs: { description: "Disallows `/**` comments longer than N characters and multi-line comments that can be merged together", recommended: true, requiresTypeChecking: false, }, schema: [ { type: "object", properties: { maxLength: { type: "number" }, }, additionalProperties: false, }, ], messages: { incorrectlyFormatted: "Comment is not formatted correctly.", }, fixable: "whitespace", }, defaultOptions: [ { /** * Matches the Airbnb style guide, which is the most popular JavaScript style guide in the * world. */ maxLength: 100, }, ], /** * We need to write the rule in such a way that it operates on the entire source code instead of * individual AST nodes: * https://stackoverflow.com/questions/47429792/is-it-possible-to-get-comments-as-nodes-in-the-ast-using-the-typescript-compiler */ create(context, [options]) { const { maxLength } = options; const comments = context.sourceCode.getAllComments(); // We only look at `/**` style comments on their own line. const jsDocComments = getJSDocComments(comments); for (const comment of jsDocComments) { const leftWhitespaceLength = comment.loc.start.column; const leftWhitespace = " ".repeat(leftWhitespaceLength); const originalComment = `${leftWhitespace}/*${comment.value}*/`; const text = getTextFromJSDocComment(comment.value); const effectiveMaxLength = maxLength - leftWhitespaceLength - " * ".length; let formattedText = formatText(text, effectiveMaxLength); // - Disallow comments like: `/** *foo */` // - We must escape the asterisk to avoid a run-time error. formattedText = trimPrefix(formattedText, String.raw `\*`, true); const canFitOnSingleLine = canFitOnSingleJSDocLine(formattedText, effectiveMaxLength); const formattedComment = canFitOnSingleLine ? getJSDocCommentSingleLine(formattedText, leftWhitespace) : getJSDocCommentMultiLine(formattedText, leftWhitespace); if (DEBUG && originalComment !== formattedComment) { console.log("originalComment:"); console.log(originalComment); console.log("formattedComment:"); console.log(formattedComment); } if (!areStringsEqualExcludingTrailingSpaces(originalComment, formattedComment)) { context.report({ loc: { start: comment.loc.start, end: comment.loc.end, }, messageId: "incorrectlyFormatted", fix: (fixer) => { const [commentStart, commentEnd] = comment.range; const commentBeginningOfLine = commentStart - comment.loc.start.column; const range = [commentBeginningOfLine, commentEnd]; return fixer.replaceTextRange(range, formattedComment); }, }); } } return {}; }, }); /** * JSDoc can be either single-line or multi-line. For example: * * ```ts * /** This is a single-line JSDoc comment. * / * * /** * * This is a multi-line JSDoc comment. * * / * ``` */ function canFitOnSingleJSDocLine(text, effectiveMaxLength) { const textLines = text.split("\n"); return (textLines.length === 1 && text.length + EXTRA_NUM_CHARACTERS_TO_FIT_ON_JSDOC_SINGLE_LINE <= effectiveMaxLength); } function getJSDocCommentSingleLine(text, leftWhitespace) { return `${leftWhitespace}/** ${text} */`; } function getJSDocCommentMultiLine(text, leftWhitespace) { const header = `${leftWhitespace}/**`; const emptyLine = `${leftWhitespace} *`; const footer = `${leftWhitespace} */`; const linePrefix = `${emptyLine} `; const lines = text.split("\n"); const commentLines = lines.map((line) => line.trim() === "" ? emptyLine : `${linePrefix}${line}`); const comments = commentLines.join("\n"); return `${header}\n${comments}\n${footer}`; }