"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _lodash = _interopRequireDefault(require("lodash")); var _WarnSettings = _interopRequireDefault(require("./WarnSettings")); var _getDefaultTagStructureForMode = _interopRequireDefault(require("./getDefaultTagStructureForMode")); var _tagNames = require("./tagNames"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } let tagStructure; const setTagStructure = mode => { tagStructure = (0, _getDefaultTagStructureForMode.default)(mode); }; // Given a nested array of property names, reduce them to a single array, // appending the name of the root element along the way if present. const flattenRoots = (params, root = '') => { let hasRestElement = false; let hasPropertyRest = false; const rests = []; const names = params.reduce((acc, cur) => { if (Array.isArray(cur)) { let nms; if (Array.isArray(cur[1])) { nms = cur[1]; } else { if (cur[1].hasRestElement) { hasRestElement = true; } if (cur[1].hasPropertyRest) { hasPropertyRest = true; } nms = cur[1].names; } const flattened = flattenRoots(nms, root ? `${root}.${cur[0]}` : cur[0]); if (flattened.hasRestElement) { hasRestElement = true; } if (flattened.hasPropertyRest) { hasPropertyRest = true; } const inner = [root ? `${root}.${cur[0]}` : cur[0], ...flattened.names].filter(Boolean); rests.push(false, ...flattened.rests); return acc.concat(inner); } if (typeof cur === 'object') { if (cur.isRestProperty) { hasPropertyRest = true; rests.push(true); } else { rests.push(false); } if (cur.restElement) { hasRestElement = true; } acc.push(root ? `${root}.${cur.name}` : cur.name); } else if (typeof cur !== 'undefined') { rests.push(false); acc.push(root ? `${root}.${cur}` : cur); } return acc; }, []); return { hasPropertyRest, hasRestElement, names, rests }; }; const getPropertiesFromPropertySignature = propSignature => { if (propSignature.type === 'TSIndexSignature' || propSignature.type === 'TSConstructSignatureDeclaration' || propSignature.type === 'TSCallSignatureDeclaration') { return undefined; } if (propSignature.typeAnnotation && propSignature.typeAnnotation.typeAnnotation.type === 'TSTypeLiteral') { return [propSignature.key.name, propSignature.typeAnnotation.typeAnnotation.members.map(member => { return getPropertiesFromPropertySignature(member); })]; } return propSignature.key.name; }; const getFunctionParameterNames = (functionNode, checkDefaultObjects) => { // eslint-disable-next-line complexity const getParamName = (param, isProperty) => { var _param$left, _param$left3; if (_lodash.default.has(param, 'typeAnnotation') || _lodash.default.has(param, 'left.typeAnnotation')) { const typeAnnotation = _lodash.default.has(param, 'left.typeAnnotation') ? param.left.typeAnnotation : param.typeAnnotation; if (typeAnnotation.typeAnnotation.type === 'TSTypeLiteral') { const propertyNames = typeAnnotation.typeAnnotation.members.map(member => { return getPropertiesFromPropertySignature(member); }); const flattened = { ...flattenRoots(propertyNames), annotationParamName: param.name }; if (_lodash.default.has(param, 'name') || _lodash.default.has(param, 'left.name')) { return [_lodash.default.has(param, 'left.name') ? param.left.name : param.name, flattened]; } return [undefined, flattened]; } } if (_lodash.default.has(param, 'name')) { return param.name; } if (_lodash.default.has(param, 'left.name')) { return param.left.name; } if (param.type === 'ObjectPattern' || ((_param$left = param.left) === null || _param$left === void 0 ? void 0 : _param$left.type) === 'ObjectPattern') { var _param$left2; const properties = param.properties || ((_param$left2 = param.left) === null || _param$left2 === void 0 ? void 0 : _param$left2.properties); const roots = properties.map(prop => { return getParamName(prop, true); }); return [undefined, flattenRoots(roots)]; } if (param.type === 'Property') { switch (param.value.type) { case 'ArrayPattern': return [param.key.name, param.value.elements.map((prop, idx) => { return { name: idx, restElement: prop.type === 'RestElement' }; })]; case 'ObjectPattern': return [param.key.name, param.value.properties.map(prop => { return getParamName(prop, isProperty); })]; case 'AssignmentPattern': { switch (param.value.left.type) { case 'Identifier': // Default parameter if (checkDefaultObjects && param.value.right.type === 'ObjectExpression') { return [param.key.name, param.value.right.properties.map(prop => { return getParamName(prop, isProperty); })]; } break; case 'ObjectPattern': return [param.key.name, param.value.left.properties.map(prop => { return getParamName(prop, isProperty); })]; case 'ArrayPattern': return [param.key.name, param.value.left.elements.map((prop, idx) => { return { name: idx, restElement: prop.type === 'RestElement' }; })]; } } } switch (param.key.type) { case 'Identifier': return param.key.name; // The key of an object could also be a string or number case 'Literal': return param.key.raw || // istanbul ignore next -- `raw` may not be present in all parsers param.key.value; // case 'MemberExpression': default: // Todo: We should really create a structure (and a corresponding // option analogous to `checkRestProperty`) which allows for // (and optionally requires) dynamic properties to have a single // line of documentation return undefined; } } if (param.type === 'ArrayPattern' || ((_param$left3 = param.left) === null || _param$left3 === void 0 ? void 0 : _param$left3.type) === 'ArrayPattern') { var _param$left4; const elements = param.elements || ((_param$left4 = param.left) === null || _param$left4 === void 0 ? void 0 : _param$left4.elements); const roots = elements.map((prop, idx) => { return { name: `"${idx}"`, restElement: (prop === null || prop === void 0 ? void 0 : prop.type) === 'RestElement' }; }); return [undefined, flattenRoots(roots)]; } if (['RestElement', 'ExperimentalRestProperty'].includes(param.type)) { return { isRestProperty: isProperty, name: param.argument.name, restElement: true }; } if (param.type === 'TSParameterProperty') { return getParamName(param.parameter, true); } throw new Error(`Unsupported function signature format: \`${param.type}\`.`); }; return (functionNode.params || functionNode.value.params).map(param => { return getParamName(param); }); }; const hasParams = functionNode => { // Should also check `functionNode.value.params` if supporting `MethodDefinition` return functionNode.params.length; }; /** * Gets all names of the target type, including those that refer to a path, e.g. * "@param foo; @param foo.bar". */ const getJsdocTagsDeep = (jsdoc, targetTagName) => { const ret = []; jsdoc.tags.forEach(({ name, tag, type }, idx) => { if (tag !== targetTagName) { return; } ret.push({ idx, name, type }); }); return ret; }; const modeWarnSettings = (0, _WarnSettings.default)(); const getTagNamesForMode = (mode, context) => { switch (mode) { case 'jsdoc': return _tagNames.jsdocTags; case 'typescript': return _tagNames.typeScriptTags; case 'closure': case 'permissive': return _tagNames.closureTags; default: if (!modeWarnSettings.hasBeenWarned(context, 'mode')) { context.report({ loc: { start: { column: 1, line: 1 } }, message: `Unrecognized value \`${mode}\` for \`settings.jsdoc.mode\`.` }); modeWarnSettings.markSettingAsWarned(context, 'mode'); } // We'll avoid breaking too many other rules return _tagNames.jsdocTags; } }; const getPreferredTagName = (context, mode, name, tagPreference = {}) => { var _Object$entries$find; const prefValues = Object.values(tagPreference); if (prefValues.includes(name) || prefValues.some(prefVal => { return prefVal && typeof prefVal === 'object' && prefVal.replacement === name; })) { return name; } // Allow keys to have a 'tag ' prefix to avoid upstream bug in ESLint // that disallows keys that conflict with Object.prototype, // e.g. 'tag constructor' for 'constructor': // https://github.com/eslint/eslint/issues/13289 // https://github.com/gajus/eslint-plugin-jsdoc/issues/537 const tagPreferenceFixed = _lodash.default.mapKeys(tagPreference, (_value, key) => { return key.replace('tag ', ''); }); if (_lodash.default.has(tagPreferenceFixed, name)) { return tagPreferenceFixed[name]; } const tagNames = getTagNamesForMode(mode, context); const preferredTagName = (_Object$entries$find = Object.entries(tagNames).find(([, aliases]) => { return aliases.includes(name); })) === null || _Object$entries$find === void 0 ? void 0 : _Object$entries$find[0]; if (preferredTagName) { return preferredTagName; } return name; }; const isValidTag = (context, mode, name, definedTags) => { const tagNames = getTagNamesForMode(mode, context); const validTagNames = Object.keys(tagNames).concat(Object.values(tagNames).flat()); const additionalTags = definedTags; const allTags = validTagNames.concat(additionalTags); return allTags.includes(name); }; const hasTag = (jsdoc, targetTagName) => { const targetTagLower = targetTagName.toLowerCase(); return _lodash.default.some(jsdoc.tags, doc => { return doc.tag.toLowerCase() === targetTagLower; }); }; const hasATag = (jsdoc, targetTagNames) => { return targetTagNames.some(targetTagName => { return hasTag(jsdoc, targetTagName); }); }; /** * Checks if the JSDoc comment declares a defined type. * * @param {JsDocTag} tag * the tag which should be checked. * @returns {boolean} * true in case a defined type is declared; otherwise false. */ const hasDefinedTypeTag = tag => { // The function should not continue in the event the type is not defined... if (typeof tag === 'undefined' || tag === null) { return false; } // .. same applies if it declares an `{undefined}` or `{void}` type const tagType = tag.type.trim(); if (tagType === 'undefined' || tagType === 'void') { return false; } // In any other case, a type is present return true; }; const ensureMap = (map, tag) => { if (!map.has(tag)) { map.set(tag, new Map()); } return map.get(tag); }; const overrideTagStructure = (structuredTags, tagMap = tagStructure) => { Object.entries(structuredTags).forEach(([tag, { name, type, required = [] }]) => { const tagStruct = ensureMap(tagMap, tag); tagStruct.set('nameContents', name); tagStruct.set('typeAllowed', type); const requiredName = required.includes('name'); if (requiredName && name === false) { throw new Error('Cannot add "name" to `require` with the tag\'s `name` set to `false`'); } tagStruct.set('nameRequired', requiredName); const requiredType = required.includes('type'); if (requiredType && type === false) { throw new Error('Cannot add "type" to `require` with the tag\'s `type` set to `false`'); } tagStruct.set('typeRequired', requiredType); const typeOrNameRequired = required.includes('typeOrNameRequired'); if (typeOrNameRequired && name === false) { throw new Error('Cannot add "typeOrNameRequired" to `require` with the tag\'s `name` set to `false`'); } if (typeOrNameRequired && type === false) { throw new Error('Cannot add "typeOrNameRequired" to `require` with the tag\'s `type` set to `false`'); } tagStruct.set('typeOrNameRequired', typeOrNameRequired); }); }; const getTagStructureForMode = (mode, structuredTags) => { const tagStruct = (0, _getDefaultTagStructureForMode.default)(mode); try { overrideTagStructure(structuredTags, tagStruct); } catch {// } return tagStruct; }; const isNamepathDefiningTag = (tag, tagMap = tagStructure) => { const tagStruct = ensureMap(tagMap, tag); return tagStruct.get('nameContents') === 'namepath-defining'; }; const tagMustHaveTypePosition = (tag, tagMap = tagStructure) => { const tagStruct = ensureMap(tagMap, tag); return tagStruct.get('typeRequired'); }; const tagMightHaveTypePosition = (tag, tagMap = tagStructure) => { if (tagMustHaveTypePosition(tag, tagMap)) { return true; } const tagStruct = ensureMap(tagMap, tag); const ret = tagStruct.get('typeAllowed'); return ret === undefined ? true : ret; }; const namepathTypes = new Set(['namepath-defining', 'namepath-referencing']); const tagMightHaveNamePosition = (tag, tagMap = tagStructure) => { const tagStruct = ensureMap(tagMap, tag); const ret = tagStruct.get('nameContents'); return ret === undefined ? true : Boolean(ret); }; const tagMightHaveNamepath = (tag, tagMap = tagStructure) => { const tagStruct = ensureMap(tagMap, tag); return namepathTypes.has(tagStruct.get('nameContents')); }; const tagMustHaveNamePosition = (tag, tagMap = tagStructure) => { const tagStruct = ensureMap(tagMap, tag); return tagStruct.get('nameRequired'); }; const tagMightHaveEitherTypeOrNamePosition = (tag, tagMap) => { return tagMightHaveTypePosition(tag, tagMap) || tagMightHaveNamepath(tag, tagMap); }; const tagMustHaveEitherTypeOrNamePosition = (tag, tagMap) => { const tagStruct = ensureMap(tagMap, tag); return tagStruct.get('typeOrNameRequired'); }; const tagMissingRequiredTypeOrNamepath = (tag, tagMap = tagStructure) => { const mustHaveTypePosition = tagMustHaveTypePosition(tag.tag, tagMap); const mightHaveTypePosition = tagMightHaveTypePosition(tag.tag, tagMap); const hasTypePosition = mightHaveTypePosition && Boolean(tag.type); const hasNameOrNamepathPosition = (tagMustHaveNamePosition(tag.tag, tagMap) || tagMightHaveNamepath(tag.tag, tagMap)) && Boolean(tag.name); const mustHaveEither = tagMustHaveEitherTypeOrNamePosition(tag.tag, tagMap); const hasEither = tagMightHaveEitherTypeOrNamePosition(tag.tag, tagMap) && (hasTypePosition || hasNameOrNamepathPosition); return mustHaveEither && !hasEither && !mustHaveTypePosition; }; /** * Checks if a node is a promise but has no resolve value or an empty value. * An `undefined` resolve does not count. * * @param {object} node * @returns {boolean} */ const isNewPromiseExpression = node => { return node.type === 'NewExpression' && node.callee.type === 'Identifier' && node.callee.name === 'Promise'; }; /** * @callback PromiseFilter * @param {object} node * @returns {boolean} */ /** * Checks if a node has a return statement. Void return does not count. * * @param {object} node * @param {PromiseFilter} promFilter * @returns {boolean|Node} */ // eslint-disable-next-line complexity const hasReturnValue = (node, promFilter) => { var _node$returnType, _node$returnType$type; if (!node) { return false; } switch (node.type) { case 'TSFunctionType': case 'TSMethodSignature': return !['TSVoidKeyword', 'TSUndefinedKeyword'].includes(node === null || node === void 0 ? void 0 : (_node$returnType = node.returnType) === null || _node$returnType === void 0 ? void 0 : (_node$returnType$type = _node$returnType.typeAnnotation) === null || _node$returnType$type === void 0 ? void 0 : _node$returnType$type.type); case 'MethodDefinition': return hasReturnValue(node.value, promFilter); case 'FunctionExpression': case 'FunctionDeclaration': case 'ArrowFunctionExpression': { return node.expression || hasReturnValue(node.body, promFilter); } case 'BlockStatement': { return node.body.some(bodyNode => { return bodyNode.type !== 'FunctionDeclaration' && hasReturnValue(bodyNode, promFilter); }); } case 'LabeledStatement': case 'WhileStatement': case 'DoWhileStatement': case 'ForStatement': case 'ForInStatement': case 'ForOfStatement': case 'WithStatement': { return hasReturnValue(node.body, promFilter); } case 'IfStatement': { return hasReturnValue(node.consequent, promFilter) || hasReturnValue(node.alternate, promFilter); } case 'TryStatement': { return hasReturnValue(node.block, promFilter) || hasReturnValue(node.handler && node.handler.body, promFilter) || hasReturnValue(node.finalizer, promFilter); } case 'SwitchStatement': { return node.cases.some(someCase => { return someCase.consequent.some(nde => { return hasReturnValue(nde, promFilter); }); }); } case 'ReturnStatement': { // void return does not count. if (node.argument === null) { return false; } if (promFilter && isNewPromiseExpression(node.argument)) { // Let caller decide how to filter, but this is, at the least, // a return of sorts and truthy return promFilter(node.argument); } return true; } default: { return false; } } }; /** * Avoids further checking child nodes if a nested function shadows the * resolver, but otherwise, if name is used (by call or passed in as an * argument to another function), will be considered as non-empty. * * This could check for redeclaration of the resolver, but as such is * unlikely, we avoid the performance cost of checking everywhere for * (re)declarations or assignments. * * @param {AST} node * @param {string} resolverName * @returns {boolean} */ // eslint-disable-next-line complexity const hasNonEmptyResolverCall = (node, resolverName) => { if (!node) { return false; } // Arrow function without block switch (node.type) { // istanbul ignore next -- In Babel? case 'OptionalCallExpression': case 'CallExpression': return node.callee.name === resolverName && ( // Implicit or expliit undefined node.arguments.length > 1 || node.arguments[0] !== undefined) || node.arguments.some(nde => { // Being passed in to another function (which might invoke it) return nde.type === 'Identifier' && nde.name === resolverName || // Handle nested items hasNonEmptyResolverCall(nde, resolverName); }); case 'ChainExpression': case 'Decorator': case 'ExpressionStatement': return hasNonEmptyResolverCall(node.expression, resolverName); case 'ClassBody': case 'BlockStatement': return node.body.some(bodyNode => { return hasNonEmptyResolverCall(bodyNode, resolverName); }); case 'FunctionExpression': case 'FunctionDeclaration': case 'ArrowFunctionExpression': { var _node$params$; // Shadowing if (((_node$params$ = node.params[0]) === null || _node$params$ === void 0 ? void 0 : _node$params$.name) === resolverName) { return false; } return hasNonEmptyResolverCall(node.body, resolverName); } case 'LabeledStatement': case 'WhileStatement': case 'DoWhileStatement': case 'ForStatement': case 'ForInStatement': case 'ForOfStatement': case 'WithStatement': { return hasNonEmptyResolverCall(node.body, resolverName); } case 'ConditionalExpression': case 'IfStatement': { return hasNonEmptyResolverCall(node.test, resolverName) || hasNonEmptyResolverCall(node.consequent, resolverName) || hasNonEmptyResolverCall(node.alternate, resolverName); } case 'TryStatement': { return hasNonEmptyResolverCall(node.block, resolverName) || hasNonEmptyResolverCall(node.handler && node.handler.body, resolverName) || hasNonEmptyResolverCall(node.finalizer, resolverName); } case 'SwitchStatement': { return node.cases.some(someCase => { return someCase.consequent.some(nde => { return hasNonEmptyResolverCall(nde, resolverName); }); }); } case 'ArrayPattern': case 'ArrayExpression': return node.elements.some(element => { return hasNonEmptyResolverCall(element, resolverName); }); case 'AssignmentPattern': return hasNonEmptyResolverCall(node.right, resolverName); case 'AssignmentExpression': case 'BinaryExpression': case 'LogicalExpression': { return hasNonEmptyResolverCall(node.left, resolverName) || hasNonEmptyResolverCall(node.right, resolverName); } // Comma case 'SequenceExpression': case 'TemplateLiteral': return node.expressions.some(subExpression => { return hasNonEmptyResolverCall(subExpression, resolverName); }); case 'ObjectPattern': case 'ObjectExpression': return node.properties.some(property => { return hasNonEmptyResolverCall(property, resolverName); }); // istanbul ignore next -- In Babel? case 'ClassMethod': case 'MethodDefinition': return node.decorators && node.decorators.some(decorator => { return hasNonEmptyResolverCall(decorator, resolverName); }) || node.computed && hasNonEmptyResolverCall(node.key, resolverName) || hasNonEmptyResolverCall(node.value, resolverName); // istanbul ignore next -- In Babel? case 'ObjectProperty': /* eslint-disable no-fallthrough */ // istanbul ignore next -- In Babel? case 'ClassProperty': /* eslint-enable no-fallthrough */ case 'Property': return node.computed && hasNonEmptyResolverCall(node.key, resolverName) || hasNonEmptyResolverCall(node.value, resolverName); // istanbul ignore next -- In Babel? case 'ObjectMethod': // istanbul ignore next -- In Babel? return node.computed && hasNonEmptyResolverCall(node.key, resolverName) || node.arguments.some(nde => { return hasNonEmptyResolverCall(nde, resolverName); }); case 'ClassExpression': case 'ClassDeclaration': return hasNonEmptyResolverCall(node.body, resolverName); case 'AwaitExpression': case 'SpreadElement': case 'UnaryExpression': case 'YieldExpression': return hasNonEmptyResolverCall(node.argument, resolverName); case 'VariableDeclaration': { return node.declarations.some(nde => { return hasNonEmptyResolverCall(nde, resolverName); }); } case 'VariableDeclarator': { return hasNonEmptyResolverCall(node.id, resolverName) || hasNonEmptyResolverCall(node.init, resolverName); } case 'TaggedTemplateExpression': return hasNonEmptyResolverCall(node.quasi, resolverName); // ?. // istanbul ignore next -- In Babel? case 'OptionalMemberExpression': case 'MemberExpression': return hasNonEmptyResolverCall(node.object, resolverName) || hasNonEmptyResolverCall(node.property, resolverName); // istanbul ignore next -- In Babel? case 'Import': case 'ImportExpression': return hasNonEmptyResolverCall(node.source, resolverName); case 'ReturnStatement': { if (node.argument === null) { return false; } return hasNonEmptyResolverCall(node.argument, resolverName); } /* // Shouldn't need to parse literals/literal components, etc. case 'Identifier': case 'TemplateElement': case 'Super': // Exports not relevant in this context */ default: return false; } }; /** * Checks if a Promise executor has no resolve value or an empty value. * An `undefined` resolve does not count. * * @param {object} node * @param {boolean} anyPromiseAsReturn * @returns {boolean} */ const hasValueOrExecutorHasNonEmptyResolveValue = (node, anyPromiseAsReturn) => { return hasReturnValue(node, prom => { if (anyPromiseAsReturn) { return true; } const [{ params, body } = {}] = prom.arguments; if (!(params !== null && params !== void 0 && params.length)) { return false; } const [{ name: resolverName }] = params; return hasNonEmptyResolverCall(body, resolverName); }); }; // eslint-disable-next-line complexity const hasNonFunctionYield = (node, checkYieldReturnValue) => { if (!node) { return false; } switch (node.type) { case 'BlockStatement': { return node.body.some(bodyNode => { return !['ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression'].includes(bodyNode.type) && hasNonFunctionYield(bodyNode, checkYieldReturnValue); }); } // istanbul ignore next -- In Babel? case 'OptionalCallExpression': case 'CallExpression': return node.arguments.some(element => { return hasNonFunctionYield(element, checkYieldReturnValue); }); case 'ChainExpression': case 'ExpressionStatement': { return hasNonFunctionYield(node.expression, checkYieldReturnValue); } case 'LabeledStatement': case 'WhileStatement': case 'DoWhileStatement': case 'ForStatement': case 'ForInStatement': case 'ForOfStatement': case 'WithStatement': { return hasNonFunctionYield(node.body, checkYieldReturnValue); } case 'ConditionalExpression': case 'IfStatement': { return hasNonFunctionYield(node.test, checkYieldReturnValue) || hasNonFunctionYield(node.consequent, checkYieldReturnValue) || hasNonFunctionYield(node.alternate, checkYieldReturnValue); } case 'TryStatement': { return hasNonFunctionYield(node.block, checkYieldReturnValue) || hasNonFunctionYield(node.handler && node.handler.body, checkYieldReturnValue) || hasNonFunctionYield(node.finalizer, checkYieldReturnValue); } case 'SwitchStatement': { return node.cases.some(someCase => { return someCase.consequent.some(nde => { return hasNonFunctionYield(nde, checkYieldReturnValue); }); }); } case 'ArrayPattern': case 'ArrayExpression': return node.elements.some(element => { return hasNonFunctionYield(element, checkYieldReturnValue); }); case 'AssignmentPattern': return hasNonFunctionYield(node.right, checkYieldReturnValue); case 'VariableDeclaration': { return node.declarations.some(nde => { return hasNonFunctionYield(nde, checkYieldReturnValue); }); } case 'VariableDeclarator': { return hasNonFunctionYield(node.id, checkYieldReturnValue) || hasNonFunctionYield(node.init, checkYieldReturnValue); } case 'AssignmentExpression': case 'BinaryExpression': case 'LogicalExpression': { return hasNonFunctionYield(node.left, checkYieldReturnValue) || hasNonFunctionYield(node.right, checkYieldReturnValue); } // Comma case 'SequenceExpression': case 'TemplateLiteral': return node.expressions.some(subExpression => { return hasNonFunctionYield(subExpression, checkYieldReturnValue); }); case 'ObjectPattern': case 'ObjectExpression': return node.properties.some(property => { return hasNonFunctionYield(property, checkYieldReturnValue); }); // istanbul ignore next -- In Babel? case 'ObjectProperty': /* eslint-disable no-fallthrough */ // istanbul ignore next -- In Babel? case 'ClassProperty': /* eslint-enable no-fallthrough */ case 'Property': return node.computed && hasNonFunctionYield(node.key, checkYieldReturnValue) || hasNonFunctionYield(node.value, checkYieldReturnValue); // istanbul ignore next -- In Babel? case 'ObjectMethod': // istanbul ignore next -- In Babel? return node.computed && hasNonFunctionYield(node.key, checkYieldReturnValue) || node.arguments.some(nde => { return hasNonFunctionYield(nde, checkYieldReturnValue); }); case 'SpreadElement': case 'UnaryExpression': return hasNonFunctionYield(node.argument, checkYieldReturnValue); case 'TaggedTemplateExpression': return hasNonFunctionYield(node.quasi, checkYieldReturnValue); // ?. // istanbul ignore next -- In Babel? case 'OptionalMemberExpression': case 'MemberExpression': return hasNonFunctionYield(node.object, checkYieldReturnValue) || hasNonFunctionYield(node.property, checkYieldReturnValue); // istanbul ignore next -- In Babel? case 'Import': case 'ImportExpression': return hasNonFunctionYield(node.source, checkYieldReturnValue); case 'ReturnStatement': { if (node.argument === null) { return false; } return hasNonFunctionYield(node.argument, checkYieldReturnValue); } case 'YieldExpression': { if (checkYieldReturnValue) { if (node.parent.type === 'VariableDeclarator') { return true; } return false; } // void return does not count. if (node.argument === null) { return false; } return true; } default: { return false; } } }; /** * Checks if a node has a return statement. Void return does not count. * * @param {object} node * @returns {boolean} */ const hasYieldValue = (node, checkYieldReturnValue) => { return node.generator && (node.expression || hasNonFunctionYield(node.body, checkYieldReturnValue)); }; /** * Checks if a node has a throws statement. * * @param {object} node * @param {boolean} innerFunction * @returns {boolean} */ // eslint-disable-next-line complexity const hasThrowValue = (node, innerFunction) => { if (!node) { return false; } // There are cases where a function may execute its inner function which // throws, but we're treating functions atomically rather than trying to // follow them switch (node.type) { case 'FunctionExpression': case 'FunctionDeclaration': case 'ArrowFunctionExpression': { return !innerFunction && !node.async && hasThrowValue(node.body, true); } case 'BlockStatement': { return node.body.some(bodyNode => { return bodyNode.type !== 'FunctionDeclaration' && hasThrowValue(bodyNode); }); } case 'LabeledStatement': case 'WhileStatement': case 'DoWhileStatement': case 'ForStatement': case 'ForInStatement': case 'ForOfStatement': case 'WithStatement': { return hasThrowValue(node.body); } case 'IfStatement': { return hasThrowValue(node.consequent) || hasThrowValue(node.alternate); } // We only consider it to throw an error if the catch or finally blocks throw an error. case 'TryStatement': { return hasThrowValue(node.handler && node.handler.body) || hasThrowValue(node.finalizer); } case 'SwitchStatement': { return node.cases.some(someCase => { return someCase.consequent.some(nde => { return hasThrowValue(nde); }); }); } case 'ThrowStatement': { return true; } default: { return false; } } }; /** @param {string} tag */ /* const isInlineTag = (tag) => { return /^(@link|@linkcode|@linkplain|@tutorial) /u.test(tag); }; */ /** * Parses GCC Generic/Template types * * @see {https://github.com/google/closure-compiler/wiki/Generic-Types} * @see {https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#template} * @param {JsDocTag} tag * @returns {Array} */ const parseClosureTemplateTag = tag => { return tag.name.split(',').map(type => { return type.trim(); }); }; /** * Checks user option for `contexts` array, defaulting to * contexts designated by the rule. Returns an array of * ESTree AST types, indicating allowable contexts. * * @param {*} context * @param {true|string[]} defaultContexts * @returns {string[]} */ const enforcedContexts = (context, defaultContexts) => { const { contexts = defaultContexts === true ? ['ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression'] : defaultContexts } = context.options[0] || {}; return contexts; }; /** * @param {string[]} contexts * @param {Function} checkJsdoc * @param {Function} handler */ const getContextObject = (contexts, checkJsdoc, handler) => { const properties = {}; contexts.forEach((prop, idx) => { if (typeof prop === 'object') { const selInfo = { lastIndex: idx, selector: prop.context }; if (prop.comment) { properties[prop.context] = checkJsdoc.bind(null, { ...selInfo, comment: prop.comment }, handler.bind(null, prop.comment)); } else { properties[prop.context] = checkJsdoc.bind(null, selInfo, null); } } else { const selInfo = { lastIndex: idx, selector: prop }; properties[prop] = checkJsdoc.bind(null, selInfo, null); } }); return properties; }; const filterTags = (tags, filter) => { return tags.filter(filter); }; const tagsWithNamesAndDescriptions = new Set(['param', 'arg', 'argument', 'property', 'prop', 'template', // These two are parsed by our custom parser as though having a `name` 'returns', 'return']); const getTagsByType = (context, mode, tags, tagPreference) => { const descName = getPreferredTagName(context, mode, 'description', tagPreference); const tagsWithoutNames = []; const tagsWithNames = filterTags(tags, tag => { const { tag: tagName } = tag; const tagWithName = tagsWithNamesAndDescriptions.has(tagName); if (!tagWithName && tagName !== descName) { tagsWithoutNames.push(tag); } return tagWithName; }); return { tagsWithNames, tagsWithoutNames }; }; const getIndent = sourceCode => { var _sourceCode$text$matc, _sourceCode$text$matc2; return ((_sourceCode$text$matc = (_sourceCode$text$matc2 = sourceCode.text.match(/^\n*([ \t]+)/u)) === null || _sourceCode$text$matc2 === void 0 ? void 0 : _sourceCode$text$matc2[1]) !== null && _sourceCode$text$matc !== void 0 ? _sourceCode$text$matc : '') + ' '; }; const isConstructor = node => { var _node$parent; return (node === null || node === void 0 ? void 0 : node.type) === 'MethodDefinition' && node.kind === 'constructor' || (node === null || node === void 0 ? void 0 : (_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : _node$parent.kind) === 'constructor'; }; const isGetter = node => { return node && node.parent.kind === 'get'; }; const isSetter = node => { return node && node.parent.kind === 'set'; }; const hasAccessorPair = node => { const { type, kind: sourceKind, key: { name: sourceName } } = node; const oppositeKind = sourceKind === 'get' ? 'set' : 'get'; const children = type === 'MethodDefinition' ? 'body' : 'properties'; return node.parent[children].some(({ kind, key: { name } }) => { return kind === oppositeKind && name === sourceName; }); }; const exemptSpeciaMethods = (jsdoc, node, context, schema) => { const hasSchemaOption = prop => { var _context$options$0$pr, _context$options$; const schemaProperties = schema[0].properties; return (_context$options$0$pr = (_context$options$ = context.options[0]) === null || _context$options$ === void 0 ? void 0 : _context$options$[prop]) !== null && _context$options$0$pr !== void 0 ? _context$options$0$pr : schemaProperties[prop] && schemaProperties[prop].default; }; const checkGetters = hasSchemaOption('checkGetters'); const checkSetters = hasSchemaOption('checkSetters'); return !hasSchemaOption('checkConstructors') && (isConstructor(node) || hasATag(jsdoc, ['class', 'constructor'])) || isGetter(node) && (!checkGetters || checkGetters === 'no-setter' && hasAccessorPair(node.parent)) || isSetter(node) && (!checkSetters || checkSetters === 'no-getter' && hasAccessorPair(node.parent)); }; /** * Since path segments may be unquoted (if matching a reserved word, * identifier or numeric literal) or single or double quoted, in either * the `@param` or in source, we need to strip the quotes to give a fair * comparison. * * @param {string} str * @returns {string} */ const dropPathSegmentQuotes = str => { return str.replace(/\.(['"])(.*)\1/gu, '.$2'); }; const comparePaths = name => { return otherPathName => { return otherPathName === name || dropPathSegmentQuotes(otherPathName) === dropPathSegmentQuotes(name); }; }; const pathDoesNotBeginWith = (name, otherPathName) => { return !name.startsWith(otherPathName) && !dropPathSegmentQuotes(name).startsWith(dropPathSegmentQuotes(otherPathName)); }; const getRegexFromString = (regexString, requiredFlags) => { const match = regexString.match(/^\/(.*)\/([gimyus]*)$/us); let flags = 'u'; let regex = regexString; if (match) { [, regex, flags] = match; if (!flags) { flags = 'u'; } } const uniqueFlags = [...new Set(flags + (requiredFlags || ''))]; flags = uniqueFlags.join(''); return new RegExp(regex, flags); }; var _default = { comparePaths, dropPathSegmentQuotes, enforcedContexts, exemptSpeciaMethods, filterTags, flattenRoots, getContextObject, getFunctionParameterNames, getIndent, getJsdocTagsDeep, getPreferredTagName, getRegexFromString, getTagsByType, getTagStructureForMode, hasATag, hasDefinedTypeTag, hasParams, hasReturnValue, hasTag, hasThrowValue, hasValueOrExecutorHasNonEmptyResolveValue, hasYieldValue, isConstructor, isGetter, isNamepathDefiningTag, isSetter, isValidTag, overrideTagStructure, parseClosureTemplateTag, pathDoesNotBeginWith, setTagStructure, tagMightHaveNamepath, tagMightHaveNamePosition, tagMightHaveTypePosition, tagMissingRequiredTypeOrNamepath, tagMustHaveNamePosition, tagMustHaveTypePosition }; exports.default = _default; module.exports = exports.default; //# sourceMappingURL=jsdocUtils.js.map