blob: d5053728e57dd6b511cefa2fbb440bf7d96e4170 [file] [log] [blame]
"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