| "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 |