UNPKG

eslint-plugin-jsdoc

Version:
287 lines (266 loc) 7.05 kB
import iterateJsdoc from '../iterateJsdoc.js'; // If supporting Node >= 10, we could loosen the default to this for the // initial letter: \\p{Upper} const matchDescriptionDefault = '^\n?([A-Z`\\d_][\\s\\S]*[.?!`]\\s*)?$'; /** * @param {string} value * @param {string} userDefault * @returns {string} */ const stringOrDefault = (value, userDefault) => { return typeof value === 'string' ? value : userDefault || matchDescriptionDefault; }; export default iterateJsdoc(({ context, jsdoc, report, utils, }) => { const { mainDescription, matchDescription, message, nonemptyTags = true, tags = {}, } = context.options[0] || {}; /** * @param {string} desc * @param {import('comment-parser').Spec} [tag] * @returns {void} */ const validateDescription = (desc, tag) => { let mainDescriptionMatch = mainDescription; let errorMessage = message; if (typeof mainDescription === 'object') { mainDescriptionMatch = mainDescription.match; errorMessage = mainDescription.message; } if (mainDescriptionMatch === false && ( !tag || !Object.prototype.hasOwnProperty.call(tags, tag.tag)) ) { return; } let tagValue = mainDescriptionMatch; if (tag) { const tagName = tag.tag; if (typeof tags[tagName] === 'object') { tagValue = tags[tagName].match; errorMessage = tags[tagName].message; } else { tagValue = tags[tagName]; } } const regex = utils.getRegexFromString( stringOrDefault(tagValue, matchDescription), ); if (!regex.test(desc)) { report( errorMessage || 'JSDoc description does not satisfy the regex pattern.', null, tag || { // Add one as description would typically be into block line: jsdoc.source[0].number + 1, }, ); } }; const { description, } = utils.getDescription(); if (description) { validateDescription(description); } /** * @param {string} tagName * @returns {boolean} */ const hasNoTag = (tagName) => { return !tags[tagName]; }; for (const tag of [ 'description', 'summary', 'file', 'classdesc', ]) { utils.forEachPreferredTag(tag, (matchingJsdocTag, targetTagName) => { const desc = (matchingJsdocTag.name + ' ' + utils.getTagDescription(matchingJsdocTag)).trim(); if (hasNoTag(targetTagName)) { validateDescription(desc, matchingJsdocTag); } }, true); } if (nonemptyTags) { for (const tag of [ 'copyright', 'example', 'see', 'todo', ]) { utils.forEachPreferredTag(tag, (matchingJsdocTag, targetTagName) => { const desc = (matchingJsdocTag.name + ' ' + utils.getTagDescription(matchingJsdocTag)).trim(); if (hasNoTag(targetTagName) && !(/.+/u).test(desc)) { report( 'JSDoc description must not be empty.', null, matchingJsdocTag, ); } }); } } if (!Object.keys(tags).length) { return; } /** * @param {string} tagName * @returns {boolean} */ const hasOptionTag = (tagName) => { return Boolean(tags[tagName]); }; const whitelistedTags = utils.filterTags(({ tag: tagName, }) => { return hasOptionTag(tagName); }); const { tagsWithNames, tagsWithoutNames, } = utils.getTagsByType(whitelistedTags); tagsWithNames.some((tag) => { const desc = /** @type {string} */ ( utils.getTagDescription(tag) ).replace(/^[- ]*/u, '') .trim(); return validateDescription(desc, tag); }); tagsWithoutNames.some((tag) => { const desc = (tag.name + ' ' + utils.getTagDescription(tag)).trim(); return validateDescription(desc, tag); }); }, { contextDefaults: true, meta: { docs: { description: 'Enforces a regular expression pattern on descriptions.', url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/match-description.md#repos-sticky-header', }, schema: [ { additionalProperties: false, properties: { contexts: { items: { anyOf: [ { type: 'string', }, { additionalProperties: false, properties: { comment: { type: 'string', }, context: { type: 'string', }, }, type: 'object', }, ], }, type: 'array', }, mainDescription: { oneOf: [ { format: 'regex', type: 'string', }, { type: 'boolean', }, { additionalProperties: false, properties: { match: { oneOf: [ { format: 'regex', type: 'string', }, { type: 'boolean', }, ], }, message: { type: 'string', }, }, type: 'object', }, ], }, matchDescription: { format: 'regex', type: 'string', }, message: { type: 'string', }, nonemptyTags: { type: 'boolean', }, tags: { patternProperties: { '.*': { oneOf: [ { format: 'regex', type: 'string', }, { enum: [ true, ], type: 'boolean', }, { additionalProperties: false, properties: { match: { oneOf: [ { format: 'regex', type: 'string', }, { enum: [ true, ], type: 'boolean', }, ], }, message: { type: 'string', }, }, type: 'object', }, ], }, }, type: 'object', }, }, type: 'object', }, ], type: 'suggestion', }, });