blob: 5cbe1e70b576402d702cf4f3700940b81005616d [file] [log] [blame]
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = iterateJsdoc;
exports.parseComment = exports.getSettings = void 0;
var _commentParser = _interopRequireWildcard(require("comment-parser"));
var _lodash = _interopRequireDefault(require("lodash"));
var _jsdocUtils = _interopRequireDefault(require("./jsdocUtils"));
var _getJSDocComment = _interopRequireWildcard(require("./eslint/getJSDocComment"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
// eslint-disable-next-line import/no-named-default
const globalState = new Map();
const skipSeeLink = parser => {
return (str, data) => {
if (data.tag === 'see' && str.match(/\{@link.+?\}/u)) {
return null;
}
return parser(str, data);
};
};
/**
*
* @param {object} commentNode
* @param {string} indent Whitespace
* @param {boolean} [trim=true]
* @returns {object}
*/
const parseComment = (commentNode, indent, trim = true) => {
// Preserve JSDoc block start/end indentation.
return (0, _commentParser.default)(`${indent}/*${commentNode.value}${indent}*/`, {
// @see https://github.com/yavorskiy/comment-parser/issues/21
parsers: [_commentParser.default.PARSERS.parse_tag, skipSeeLink((str, data) => {
if (['default', 'defaultvalue'].includes(data.tag)) {
return null;
}
return _commentParser.default.PARSERS.parse_type(str, data);
}), skipSeeLink((str, data) => {
if (['example', 'return', 'returns', 'throws', 'exception', 'access', 'version', 'since', 'license', 'author', 'default', 'defaultvalue'].includes(data.tag)) {
return null;
}
return _commentParser.default.PARSERS.parse_name(str, data);
}), // parse_description
(str, data) => {
// Only expected throw in previous step is if bad name (i.e.,
// missing end bracket on optional name), but `@example`
// skips name parsing
/* istanbul ignore next */
if (data.errors && data.errors.length) {
return null;
} // Tweak original regex to capture only single optional space
const result = str.match(/^ ?((.|\s)+)?/u); // Always has at least whitespace due to `indent` we've added
/* istanbul ignore next */
if (result) {
return {
data: {
description: result[1] === undefined ? '' : result[1]
},
source: result[0]
};
} // Always has at least whitespace due to `indent` we've added
/* istanbul ignore next */
return null;
}],
trim
})[0] || {};
};
exports.parseComment = parseComment;
const getBasicUtils = (context, {
tagNamePreference,
mode
}) => {
const utils = {};
utils.reportSettings = message => {
context.report({
loc: {
start: {
column: 1,
line: 1
}
},
message
});
};
utils.getPreferredTagNameObject = ({
tagName
}) => {
const ret = _jsdocUtils.default.getPreferredTagName(context, mode, tagName, tagNamePreference);
const isObject = ret && typeof ret === 'object';
if (ret === false || isObject && !ret.replacement) {
return {
blocked: true,
tagName
};
}
return ret;
};
return utils;
};
const getUtils = (node, jsdoc, jsdocNode, settings, report, context, iteratingAll) => {
const ancestors = context.getAncestors();
const sourceCode = context.getSourceCode();
const utils = getBasicUtils(context, settings);
const tagNamePreference = settings.tagNamePreference,
overrideReplacesDocs = settings.overrideReplacesDocs,
implementsReplacesDocs = settings.implementsReplacesDocs,
augmentsExtendsReplacesDocs = settings.augmentsExtendsReplacesDocs,
maxLines = settings.maxLines,
minLines = settings.minLines,
mode = settings.mode;
utils.isIteratingFunction = () => {
return !iteratingAll || ['ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression'].includes(node && node.type);
};
utils.isVirtualFunction = () => {
return iteratingAll && utils.hasATag(['callback', 'function', 'func', 'method']);
};
utils.stringify = tagBlock => {
const indent = _jsdocUtils.default.getIndent(sourceCode);
return (0, _commentParser.stringify)([tagBlock], {
indent
}).slice(indent.length - 1);
};
utils.reportJSDoc = (msg, tag, handler) => {
report(msg, handler ? fixer => {
handler();
const replacement = utils.stringify(jsdoc);
return fixer.replaceText(jsdocNode, replacement);
} : null, tag);
};
utils.getFunctionParameterNames = () => {
return _jsdocUtils.default.getFunctionParameterNames(node);
};
utils.isConstructor = () => {
return node && node.parent && node.parent.kind === 'constructor';
};
utils.isSetter = () => {
return node && node.parent.kind === 'set';
};
utils.getJsdocTagsDeep = tagName => {
const name = utils.getPreferredTagName({
tagName
});
if (!name) {
return false;
}
return _jsdocUtils.default.getJsdocTagsDeep(jsdoc, name);
};
utils.getJsdocTags = tagName => {
const name = utils.getPreferredTagName({
tagName
});
if (!name) {
return false;
}
return _jsdocUtils.default.getJsdocTags(jsdoc, name);
};
utils.getPreferredTagName = ({
tagName,
skipReportingBlockedTag = false,
allowObjectReturn = false,
defaultMessage = `Unexpected tag \`@${tagName}\``
}) => {
const ret = _jsdocUtils.default.getPreferredTagName(context, mode, tagName, tagNamePreference);
const isObject = ret && typeof ret === 'object';
if (utils.hasTag(tagName) && (ret === false || isObject && !ret.replacement)) {
if (skipReportingBlockedTag) {
return {
blocked: true,
tagName
};
}
const message = isObject && ret.message || defaultMessage;
report(message, null, utils.getTags(tagName)[0]);
return false;
}
return isObject && !allowObjectReturn ? ret.replacement : ret;
};
utils.isValidTag = (name, definedTags) => {
return _jsdocUtils.default.isValidTag(context, mode, name, definedTags);
};
utils.hasATag = name => {
return _jsdocUtils.default.hasATag(jsdoc, name);
};
utils.hasTag = name => {
return _jsdocUtils.default.hasTag(jsdoc, name);
};
utils.avoidDocs = () => {
if (overrideReplacesDocs !== false && (utils.hasTag('override') || utils.classHasTag('override')) || implementsReplacesDocs !== false && (utils.hasTag('implements') || utils.classHasTag('implements')) || // inheritdoc implies that all documentation is inherited; see https://jsdoc.app/tags-inheritdoc.html
utils.hasTag('inheritdoc') || augmentsExtendsReplacesDocs && (utils.hasATag(['augments', 'extends']) || utils.classHasTag('augments') || utils.classHasTag('extends'))) {
return true;
}
const exemptedBy = _lodash.default.get(context, 'options[0].exemptedBy');
if (exemptedBy && exemptedBy.length && utils.getPresentTags(exemptedBy).length) {
return true;
}
return false;
};
utils.tagMustHaveEitherTypeOrNamePosition = tagName => {
return _jsdocUtils.default.tagMustHaveEitherTypeOrNamePosition(tagName);
};
utils.tagMightHaveEitherTypeOrNamePosition = tagName => {
return _jsdocUtils.default.tagMightHaveEitherTypeOrNamePosition(mode, tagName);
};
utils.tagMustHaveNamePosition = tagName => {
return _jsdocUtils.default.tagMustHaveNamePosition(tagName);
};
utils.tagMightHaveNamePosition = tagName => {
return _jsdocUtils.default.tagMightHaveNamePosition(tagName);
};
utils.tagMustHaveTypePosition = tagName => {
return _jsdocUtils.default.tagMustHaveTypePosition(mode, tagName);
};
utils.tagMightHaveTypePosition = tagName => {
return _jsdocUtils.default.tagMightHaveTypePosition(mode, tagName);
};
utils.isNamepathDefiningTag = tagName => {
return _jsdocUtils.default.isNamepathDefiningTag(tagName);
};
utils.hasDefinedTypeReturnTag = tag => {
return _jsdocUtils.default.hasDefinedTypeReturnTag(tag);
};
utils.hasReturnValue = () => {
return _jsdocUtils.default.hasReturnValue(node);
};
utils.isAsync = () => {
return node.async;
};
utils.getTags = tagName => {
return utils.filterTags(item => {
return item.tag === tagName;
});
};
utils.getPresentTags = tagList => {
return utils.filterTags(tag => {
return tagList.includes(tag.tag);
});
};
utils.filterTags = filter => {
return _jsdocUtils.default.filterTags(jsdoc.tags, filter);
};
utils.getTagsByType = tags => {
return _jsdocUtils.default.getTagsByType(context, mode, tags, tagNamePreference);
};
utils.hasOptionTag = tagName => {
const tags = _lodash.default.get(context, 'options[0].tags');
return Boolean(tags && tags.includes(tagName));
};
utils.getClassNode = () => {
return [...ancestors, node].reverse().find(parent => {
return parent && ['ClassDeclaration', 'ClassExpression'].includes(parent.type);
}) || null;
};
utils.getClassJsdoc = () => {
const classNode = utils.getClassNode();
if (!classNode) {
return null;
}
const classJsdocNode = (0, _getJSDocComment.default)(sourceCode, classNode, {
maxLines,
minLines
});
if (classJsdocNode) {
const indent = ' '.repeat(classJsdocNode.loc.start.column);
return parseComment(classJsdocNode, indent);
}
return null;
};
utils.classHasTag = tagName => {
const classJsdoc = utils.getClassJsdoc();
return Boolean(classJsdoc) && _jsdocUtils.default.hasTag(classJsdoc, tagName);
};
utils.forEachPreferredTag = (tagName, arrayHandler, skipReportingBlockedTag = false) => {
const targetTagName = utils.getPreferredTagName({
skipReportingBlockedTag,
tagName
});
if (!targetTagName || skipReportingBlockedTag && targetTagName && typeof targetTagName === 'object') {
return;
}
const matchingJsdocTags = _lodash.default.filter(jsdoc.tags || [], {
tag: targetTagName
});
matchingJsdocTags.forEach(matchingJsdocTag => {
arrayHandler(matchingJsdocTag, targetTagName);
});
};
return utils;
};
const getSettings = context => {
/* eslint-disable sort-keys-fix/sort-keys-fix */
const settings = {
// All rules
ignorePrivate: Boolean(_lodash.default.get(context, 'settings.jsdoc.ignorePrivate')),
maxLines: Number(_lodash.default.get(context, 'settings.jsdoc.maxLines', 1)),
minLines: Number(_lodash.default.get(context, 'settings.jsdoc.minLines', 0)),
// `check-tag-names` and many returns/param rules
tagNamePreference: _lodash.default.get(context, 'settings.jsdoc.tagNamePreference') || {},
// `check-types` and `no-undefined-types`
preferredTypes: _lodash.default.get(context, 'settings.jsdoc.preferredTypes') || {},
// `require-param`, `require-description`, `require-example`, `require-returns`
overrideReplacesDocs: _lodash.default.get(context, 'settings.jsdoc.overrideReplacesDocs'),
implementsReplacesDocs: _lodash.default.get(context, 'settings.jsdoc.implementsReplacesDocs'),
augmentsExtendsReplacesDocs: _lodash.default.get(context, 'settings.jsdoc.augmentsExtendsReplacesDocs'),
// Many rules, e.g., `check-tag-names`
mode: _lodash.default.get(context, 'settings.jsdoc.mode') || 'jsdoc'
};
/* eslint-enable sort-keys-fix/sort-keys-fix */
return settings;
};
/**
* Create the report function
*
* @param {object} context
* @param {object} commentNode
*/
exports.getSettings = getSettings;
const makeReport = (context, commentNode) => {
const report = (message, fix = null, jsdocLoc = null, data = null) => {
let loc;
if (jsdocLoc) {
const lineNumber = commentNode.loc.start.line + jsdocLoc.line;
loc = {
end: {
line: lineNumber
},
start: {
line: lineNumber
}
};
if (jsdocLoc.column) {
const colNumber = commentNode.loc.start.column + jsdocLoc.column;
loc.end.column = colNumber;
loc.start.column = colNumber;
}
}
context.report({
data,
fix,
loc,
message,
node: commentNode
});
};
return report;
};
/**
* @typedef {ReturnType<typeof getUtils>} Utils
* @typedef {ReturnType<typeof getSettings>} Settings
* @typedef {(
* arg: {
* context: object,
* sourceCode: object,
* indent: string,
* jsdoc: object,
* jsdocNode: object,
* node: object | null,
* report: ReturnType<typeof makeReport>,
* settings: Settings,
* utils: Utils,
* }
* ) => any } JsdocVisitor
*/
const iterate = (ruleConfig, context, lines, jsdocNode, node, settings, sourceCode, iterator, state, iteratingAll) => {
const sourceLine = lines[jsdocNode.loc.start.line - 1];
const indent = sourceLine.charAt(0).repeat(jsdocNode.loc.start.column);
const jsdoc = parseComment(jsdocNode, indent, !ruleConfig.noTrim);
const report = makeReport(context, jsdocNode);
const utils = getUtils(node, jsdoc, jsdocNode, settings, report, context, iteratingAll);
if (settings.ignorePrivate && !ruleConfig.checkPrivate && (utils.hasTag('private') || _lodash.default.filter(jsdoc.tags, {
tag: 'access'
}).some(({
description
}) => {
return description === 'private';
}))) {
return;
}
iterator({
context,
globalState,
indent,
iteratingAll,
jsdoc,
jsdocNode,
node,
report,
settings,
sourceCode,
state,
utils
});
};
/**
* Create an eslint rule that iterates over all JSDocs, regardless of whether
* they are attached to a function-like node.
*
* @param {JsdocVisitor} iterator
* @param {{meta: any}} ruleConfig
*/
const iterateAllJsdocs = (iterator, ruleConfig) => {
const trackedJsdocs = [];
const callIterator = (context, node, jsdocNodes, state, lastCall) => {
const sourceCode = context.getSourceCode();
const settings = getSettings(context);
const lines = sourceCode.lines;
const utils = getBasicUtils(context, settings);
jsdocNodes.forEach(jsdocNode => {
if (!/^\/\*\*\s/.test(sourceCode.getText(jsdocNode))) {
return;
}
iterate(ruleConfig, context, lines, jsdocNode, node, settings, sourceCode, iterator, state, true);
});
if (lastCall && ruleConfig.exit) {
ruleConfig.exit({
context,
state,
utils
});
}
};
return {
create(context) {
const sourceCode = context.getSourceCode();
const settings = getSettings(context);
const state = {};
return {
'*:not(Program)'(node) {
const reducedNode = (0, _getJSDocComment.getReducedASTNode)(node, sourceCode);
if (node !== reducedNode) {
return;
}
const comment = (0, _getJSDocComment.default)(sourceCode, node, settings);
if (trackedJsdocs.includes(comment)) {
return;
}
if (!comment) {
if (ruleConfig.nonComment) {
ruleConfig.nonComment({
node,
state
});
}
return;
}
trackedJsdocs.push(comment);
callIterator(context, node, [comment], state);
},
'Program:exit'() {
const allComments = sourceCode.getAllComments();
const untrackedJSdoc = allComments.filter(node => {
return !trackedJsdocs.includes(node);
});
callIterator(context, null, untrackedJSdoc, state, true);
}
};
},
meta: ruleConfig.meta
};
};
/**
* Create an eslint rule that iterates over all JSDocs, regardless of whether
* they are attached to a function-like node.
*
* @param {JsdocVisitor} iterator
* @param {{meta: any}} ruleConfig
*/
const checkFile = (iterator, ruleConfig) => {
return {
create(context) {
const sourceCode = context.getSourceCode();
const settings = getSettings(context);
return {
'Program:exit'() {
const allComments = sourceCode.getAllComments();
const lines = sourceCode.lines;
const utils = getBasicUtils(context, settings);
iterator({
allComments,
context,
lines,
makeReport,
settings,
sourceCode,
utils
});
}
};
},
meta: ruleConfig.meta
};
};
/**
* @param {JsdocVisitor} iterator
* @param {{
* meta: any,
* contextDefaults?: true | string[],
* iterateAllJsdocs?: true,
* }} ruleConfig
*/
function iterateJsdoc(iterator, ruleConfig) {
const metaType = _lodash.default.get(ruleConfig, 'meta.type');
if (!metaType || !['problem', 'suggestion', 'layout'].includes(metaType)) {
throw new TypeError('Rule must include `meta.type` option (with value "problem", "suggestion", or "layout")');
}
if (typeof iterator !== 'function') {
throw new TypeError('The iterator argument must be a function.');
}
if (ruleConfig.checkFile) {
return checkFile(iterator, ruleConfig);
}
if (ruleConfig.iterateAllJsdocs) {
return iterateAllJsdocs(iterator, ruleConfig);
}
return {
/**
* The entrypoint for the JSDoc rule.
*
* @param {*} context
* a reference to the context which hold all important information
* like settings and the sourcecode to check.
* @returns {object}
* a list with parser callback function.
*/
create(context) {
let contexts;
if (ruleConfig.contextDefaults) {
contexts = _jsdocUtils.default.enforcedContexts(context, ruleConfig.contextDefaults);
if (contexts.includes('any')) {
return iterateAllJsdocs(iterator, ruleConfig).create(context);
}
}
const sourceCode = context.getSourceCode();
const settings = getSettings(context);
const lines = sourceCode.lines;
const checkJsdoc = node => {
const jsdocNode = (0, _getJSDocComment.default)(sourceCode, node, settings);
if (!jsdocNode) {
return;
}
iterate(ruleConfig, context, lines, jsdocNode, node, settings, sourceCode, iterator);
};
if (ruleConfig.contextDefaults) {
return _jsdocUtils.default.getContextObject(contexts, checkJsdoc);
}
return {
ArrowFunctionExpression: checkJsdoc,
FunctionDeclaration: checkJsdoc,
FunctionExpression: checkJsdoc
};
},
meta: ruleConfig.meta
};
}
//# sourceMappingURL=iterateJsdoc.js.map