| "use strict"; |
| |
| Object.defineProperty(exports, "__esModule", { |
| value: true |
| }); |
| exports.default = void 0; |
| |
| var _lodash = _interopRequireDefault(require("lodash")); |
| |
| var _mainUmd = require("regextras/dist/main-umd"); |
| |
| var _iterateJsdoc = _interopRequireDefault(require("../iterateJsdoc")); |
| |
| function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } |
| |
| const otherDescriptiveTags = [// 'copyright' and 'see' might be good addition, but as the former may be |
| // sensitive text, and the latter may have just a link, they are not |
| // included by default |
| 'summary', 'file', 'fileoverview', 'overview', 'classdesc', 'todo', 'deprecated', 'throws', 'exception', 'yields', 'yield']; |
| |
| const extractParagraphs = text => { |
| // Todo [engine:node@>8.11.0]: Uncomment following line with neg. lookbehind instead |
| // return text.split(/(?<![;:])\n\n/u); |
| return [...text].reverse().join('').split(/\n\n(?![;:])/u).map(par => { |
| return [...par].reverse().join(''); |
| }).reverse(); |
| }; |
| |
| const extractSentences = (text, abbreviationsRegex) => { |
| const txt = text // Remove all {} tags. |
| .replace(/\{[\s\S]*?\}\s*/gu, '') // Remove custom abbreviations |
| .replace(abbreviationsRegex, ''); |
| const sentenceEndGrouping = /([.?!])(?:\s+|$)/u; |
| const puncts = (0, _mainUmd.RegExtras)(sentenceEndGrouping).map(txt, punct => { |
| return punct; |
| }); |
| return txt.split(/[.?!](?:\s+|$)/u) // Re-add the dot. |
| .map((sentence, idx) => { |
| return /^\s*$/u.test(sentence) ? sentence : `${sentence}${puncts[idx] || ''}`; |
| }); |
| }; |
| |
| const isNewLinePrecededByAPeriod = text => { |
| let lastLineEndsSentence; |
| const lines = text.split('\n'); |
| return !lines.some(line => { |
| if (lastLineEndsSentence === false && /^[A-Z][a-z]/u.test(line)) { |
| return true; |
| } |
| |
| lastLineEndsSentence = /[.:?!|]$/u.test(line); |
| return false; |
| }); |
| }; |
| |
| const isCapitalized = str => { |
| return str[0] === str[0].toUpperCase(); |
| }; |
| |
| const isTable = str => { |
| return str.charAt() === '|'; |
| }; |
| |
| const capitalize = str => { |
| return str.charAt(0).toUpperCase() + str.slice(1); |
| }; |
| |
| const validateDescription = (description, reportOrig, jsdocNode, abbreviationsRegex, sourceCode, tag) => { |
| if (!description) { |
| return false; |
| } |
| |
| const paragraphs = extractParagraphs(description); |
| return paragraphs.some((paragraph, parIdx) => { |
| const sentences = extractSentences(paragraph, abbreviationsRegex); |
| |
| const fix = fixer => { |
| let text = sourceCode.getText(jsdocNode); |
| |
| if (!/[.:?!]$/u.test(paragraph)) { |
| const line = _lodash.default.last(paragraph.split('\n')); |
| |
| text = text.replace(new RegExp(`${_lodash.default.escapeRegExp(line)}$`, 'mu'), `${line}.`); |
| } |
| |
| var _iteratorNormalCompletion = true; |
| var _didIteratorError = false; |
| var _iteratorError = undefined; |
| |
| try { |
| for (var _iterator = sentences.filter(sentence_ => { |
| return !/^\s*$/u.test(sentence_) && !isCapitalized(sentence_) && !isTable(sentence_); |
| })[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { |
| const sentence = _step.value; |
| const beginning = sentence.split('\n')[0]; |
| |
| if (tag.tag) { |
| const reg = new RegExp(`(@${_lodash.default.escapeRegExp(tag.tag)}.*)${_lodash.default.escapeRegExp(beginning)}`, 'u'); |
| text = text.replace(reg, ($0, $1) => { |
| return $1 + capitalize(beginning); |
| }); |
| } else { |
| text = text.replace(beginning, capitalize(beginning)); |
| } |
| } |
| } catch (err) { |
| _didIteratorError = true; |
| _iteratorError = err; |
| } finally { |
| try { |
| if (!_iteratorNormalCompletion && _iterator.return != null) { |
| _iterator.return(); |
| } |
| } finally { |
| if (_didIteratorError) { |
| throw _iteratorError; |
| } |
| } |
| } |
| |
| return fixer.replaceText(jsdocNode, text); |
| }; |
| |
| const report = (msg, fixer, tagObj) => { |
| tagObj.line += parIdx * 2; // Avoid errors if old column doesn't exist here |
| |
| tagObj.column = 0; |
| reportOrig(msg, fixer, tagObj); |
| }; |
| |
| if (sentences.some(sentence => { |
| return !/^\s*$/u.test(sentence) && !isCapitalized(sentence) && !isTable(sentence); |
| })) { |
| report('Sentence should start with an uppercase character.', fix, tag); |
| } |
| |
| const paragraphNoAbbreviations = paragraph.replace(abbreviationsRegex, ''); |
| |
| if (!/[.!?|]$/u.test(paragraphNoAbbreviations)) { |
| report('Sentence must end with a period.', fix, tag); |
| return true; |
| } |
| |
| if (!isNewLinePrecededByAPeriod(paragraphNoAbbreviations)) { |
| report('A line of text is started with an uppercase character, but preceding line does not end the sentence.', null, tag); |
| return true; |
| } |
| |
| return false; |
| }); |
| }; |
| |
| var _default = (0, _iterateJsdoc.default)(({ |
| sourceCode, |
| context, |
| jsdoc, |
| report, |
| jsdocNode, |
| utils |
| }) => { |
| const options = context.options[0] || {}; |
| const _options$abbreviation = options.abbreviations, |
| abbreviations = _options$abbreviation === void 0 ? [] : _options$abbreviation; |
| const abbreviationsRegex = abbreviations.length ? new RegExp('\\b' + abbreviations.map(abbreviation => { |
| return _lodash.default.escapeRegExp(abbreviation.replace(/\.$/g, '') + '.'); |
| }).join('|') + '(?:$|\\s)', 'gu') : ''; |
| |
| if (!jsdoc.tags || validateDescription(jsdoc.description, report, jsdocNode, abbreviationsRegex, sourceCode, { |
| line: jsdoc.line + 1 |
| })) { |
| return; |
| } |
| |
| utils.forEachPreferredTag('description', matchingJsdocTag => { |
| const description = `${matchingJsdocTag.name} ${matchingJsdocTag.description}`.trim(); |
| validateDescription(description, report, jsdocNode, abbreviationsRegex, sourceCode, matchingJsdocTag); |
| }, true); |
| |
| const _utils$getTagsByType = utils.getTagsByType(jsdoc.tags), |
| tagsWithNames = _utils$getTagsByType.tagsWithNames; |
| |
| const tagsWithoutNames = utils.filterTags(({ |
| tag: tagName |
| }) => { |
| return otherDescriptiveTags.includes(tagName) || utils.hasOptionTag(tagName) && !tagsWithNames.some(({ |
| tag |
| }) => { |
| // If user accidentally adds tags with names (or like `returns` |
| // get parsed as having names), do not add to this list |
| return tag === tagName; |
| }); |
| }); |
| tagsWithNames.some(tag => { |
| const description = _lodash.default.trimStart(tag.description, '- '); |
| |
| return validateDescription(description, report, jsdocNode, abbreviationsRegex, sourceCode, tag); |
| }); |
| tagsWithoutNames.some(tag => { |
| const description = `${tag.name} ${tag.description}`.trim(); |
| return validateDescription(description, report, jsdocNode, abbreviationsRegex, sourceCode, tag); |
| }); |
| }, { |
| iterateAllJsdocs: true, |
| meta: { |
| fixable: 'code', |
| schema: [{ |
| additionalProperties: false, |
| properties: { |
| abbreviations: { |
| items: { |
| type: 'string' |
| }, |
| type: 'array' |
| }, |
| tags: { |
| items: { |
| type: 'string' |
| }, |
| type: 'array' |
| } |
| }, |
| type: 'object' |
| }], |
| type: 'suggestion' |
| } |
| }); |
| |
| exports.default = _default; |
| module.exports = exports.default; |
| //# sourceMappingURL=requireDescriptionCompleteSentence.js.map |