matchDescription.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. var _iterateJsdoc = _interopRequireDefault(require("../iterateJsdoc.js"));
  7. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  8. // If supporting Node >= 10, we could loosen the default to this for the
  9. // initial letter: \\p{Upper}
  10. const matchDescriptionDefault = '^\n?([A-Z`\\d_][\\s\\S]*[.?!`]\\s*)?$';
  11. /**
  12. * @param {string} value
  13. * @param {string} userDefault
  14. * @returns {string}
  15. */
  16. const stringOrDefault = (value, userDefault) => {
  17. return typeof value === 'string' ? value : userDefault || matchDescriptionDefault;
  18. };
  19. var _default = exports.default = (0, _iterateJsdoc.default)(({
  20. jsdoc,
  21. report,
  22. context,
  23. utils
  24. }) => {
  25. const {
  26. mainDescription,
  27. matchDescription,
  28. message,
  29. nonemptyTags = true,
  30. tags = {}
  31. } = context.options[0] || {};
  32. /**
  33. * @param {string} desc
  34. * @param {import('comment-parser').Spec} [tag]
  35. * @returns {void}
  36. */
  37. const validateDescription = (desc, tag) => {
  38. let mainDescriptionMatch = mainDescription;
  39. let errorMessage = message;
  40. if (typeof mainDescription === 'object') {
  41. mainDescriptionMatch = mainDescription.match;
  42. errorMessage = mainDescription.message;
  43. }
  44. if (mainDescriptionMatch === false && (!tag || !Object.prototype.hasOwnProperty.call(tags, tag.tag))) {
  45. return;
  46. }
  47. let tagValue = mainDescriptionMatch;
  48. if (tag) {
  49. const tagName = tag.tag;
  50. if (typeof tags[tagName] === 'object') {
  51. tagValue = tags[tagName].match;
  52. errorMessage = tags[tagName].message;
  53. } else {
  54. tagValue = tags[tagName];
  55. }
  56. }
  57. const regex = utils.getRegexFromString(stringOrDefault(tagValue, matchDescription));
  58. if (!regex.test(desc)) {
  59. report(errorMessage || 'JSDoc description does not satisfy the regex pattern.', null, tag || {
  60. // Add one as description would typically be into block
  61. line: jsdoc.source[0].number + 1
  62. });
  63. }
  64. };
  65. const {
  66. description
  67. } = utils.getDescription();
  68. if (description) {
  69. validateDescription(description);
  70. }
  71. /**
  72. * @param {string} tagName
  73. * @returns {boolean}
  74. */
  75. const hasNoTag = tagName => {
  76. return !tags[tagName];
  77. };
  78. for (const tag of ['description', 'summary', 'file', 'classdesc']) {
  79. utils.forEachPreferredTag(tag, (matchingJsdocTag, targetTagName) => {
  80. const desc = (matchingJsdocTag.name + ' ' + utils.getTagDescription(matchingJsdocTag)).trim();
  81. if (hasNoTag(targetTagName)) {
  82. validateDescription(desc, matchingJsdocTag);
  83. }
  84. }, true);
  85. }
  86. if (nonemptyTags) {
  87. for (const tag of ['copyright', 'example', 'see', 'todo']) {
  88. utils.forEachPreferredTag(tag, (matchingJsdocTag, targetTagName) => {
  89. const desc = (matchingJsdocTag.name + ' ' + utils.getTagDescription(matchingJsdocTag)).trim();
  90. if (hasNoTag(targetTagName) && !/.+/u.test(desc)) {
  91. report('JSDoc description must not be empty.', null, matchingJsdocTag);
  92. }
  93. });
  94. }
  95. }
  96. if (!Object.keys(tags).length) {
  97. return;
  98. }
  99. /**
  100. * @param {string} tagName
  101. * @returns {boolean}
  102. */
  103. const hasOptionTag = tagName => {
  104. return Boolean(tags[tagName]);
  105. };
  106. const whitelistedTags = utils.filterTags(({
  107. tag: tagName
  108. }) => {
  109. return hasOptionTag(tagName);
  110. });
  111. const {
  112. tagsWithNames,
  113. tagsWithoutNames
  114. } = utils.getTagsByType(whitelistedTags);
  115. tagsWithNames.some(tag => {
  116. const desc = /** @type {string} */utils.getTagDescription(tag).replace(/^[- ]*/u, '').trim();
  117. return validateDescription(desc, tag);
  118. });
  119. tagsWithoutNames.some(tag => {
  120. const desc = (tag.name + ' ' + utils.getTagDescription(tag)).trim();
  121. return validateDescription(desc, tag);
  122. });
  123. }, {
  124. contextDefaults: true,
  125. meta: {
  126. docs: {
  127. description: 'Enforces a regular expression pattern on descriptions.',
  128. url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/match-description.md#repos-sticky-header'
  129. },
  130. schema: [{
  131. additionalProperties: false,
  132. properties: {
  133. contexts: {
  134. items: {
  135. anyOf: [{
  136. type: 'string'
  137. }, {
  138. additionalProperties: false,
  139. properties: {
  140. comment: {
  141. type: 'string'
  142. },
  143. context: {
  144. type: 'string'
  145. }
  146. },
  147. type: 'object'
  148. }]
  149. },
  150. type: 'array'
  151. },
  152. mainDescription: {
  153. oneOf: [{
  154. format: 'regex',
  155. type: 'string'
  156. }, {
  157. type: 'boolean'
  158. }, {
  159. additionalProperties: false,
  160. properties: {
  161. match: {
  162. oneOf: [{
  163. format: 'regex',
  164. type: 'string'
  165. }, {
  166. type: 'boolean'
  167. }]
  168. },
  169. message: {
  170. type: 'string'
  171. }
  172. },
  173. type: 'object'
  174. }]
  175. },
  176. matchDescription: {
  177. format: 'regex',
  178. type: 'string'
  179. },
  180. message: {
  181. type: 'string'
  182. },
  183. nonemptyTags: {
  184. type: 'boolean'
  185. },
  186. tags: {
  187. patternProperties: {
  188. '.*': {
  189. oneOf: [{
  190. format: 'regex',
  191. type: 'string'
  192. }, {
  193. enum: [true],
  194. type: 'boolean'
  195. }, {
  196. additionalProperties: false,
  197. properties: {
  198. match: {
  199. oneOf: [{
  200. format: 'regex',
  201. type: 'string'
  202. }, {
  203. enum: [true],
  204. type: 'boolean'
  205. }]
  206. },
  207. message: {
  208. type: 'string'
  209. }
  210. },
  211. type: 'object'
  212. }]
  213. }
  214. },
  215. type: 'object'
  216. }
  217. },
  218. type: 'object'
  219. }],
  220. type: 'suggestion'
  221. }
  222. });
  223. module.exports = exports.default;
  224. //# sourceMappingURL=matchDescription.js.map