blob: 733b051f1c0297c491a9b99a1e38f8cb693d982c [file]
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _jsdoccomment = require("@es-joy/jsdoccomment");
var _WarnSettings = _interopRequireDefault(require("./WarnSettings"));
var _getDefaultTagStructureForMode = _interopRequireDefault(require("./getDefaultTagStructureForMode"));
var _tagNames = require("./tagNames");
var _hasReturnValue = require("./utils/hasReturnValue");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/* eslint-disable jsdoc/no-undefined-types */
/**
* @typedef {"jsdoc"|"typescript"|"closure"} ParserMode
*/
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
};
};
/**
* @param {object} propSignature
* @returns {undefined|Array|string}
*/
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;
};
/**
* @param {object} functionNode
* @param {boolean} checkDefaultObjects
* @returns {Array}
*/
const getFunctionParameterNames = (functionNode, checkDefaultObjects) => {
var _functionNode$value;
// eslint-disable-next-line complexity
const getParamName = (param, isProperty) => {
var _param$left, _param$left3;
const hasLeftTypeAnnotation = 'left' in param && 'typeAnnotation' in param.left;
if ('typeAnnotation' in param || hasLeftTypeAnnotation) {
const typeAnnotation = hasLeftTypeAnnotation ? 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
};
const hasLeftName = 'left' in param && 'name' in param.left;
if ('name' in param || hasLeftName) {
return [hasLeftName ? param.left.name : param.name, flattened];
}
return [undefined, flattened];
}
}
if ('name' in param) {
return param.name;
}
if ('left' in param && 'name' in param.left) {
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') {
// eslint-disable-next-line default-case
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':
{
// eslint-disable-next-line default-case
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}\`.`);
};
if (!functionNode) {
return [];
}
return (functionNode.params || ((_functionNode$value = functionNode.value) === null || _functionNode$value === void 0 ? void 0 : _functionNode$value.params) || []).map(param => {
return getParamName(param);
});
};
/**
* @param {Node} functionNode
* @returns {Integer}
*/
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".
*
* @param {object} jsdoc
* @param {string} targetTagName
* @returns {Array<object>}
*/
const getJsdocTagsDeep = (jsdoc, targetTagName) => {
const ret = [];
for (const [idx, {
name,
tag,
type
}] of jsdoc.tags.entries()) {
if (tag !== targetTagName) {
continue;
}
ret.push({
idx,
name,
type
});
}
return ret;
};
const modeWarnSettings = (0, _WarnSettings.default)();
/**
* @param {string} mode
* @param context
*/
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;
}
};
/**
* @param context
* @param {ParserMode} mode
* @param {string} name
* @param {object} tagPreference
* @returns {string|object}
*/
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 = Object.fromEntries(Object.entries(tagPreference).map(([key, value]) => {
return [key.replace(/^tag /u, ''), value];
}));
if (Object.prototype.hasOwnProperty.call(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;
};
/**
* @param context
* @param {ParserMode} mode
* @param {string} name
* @param {Array} definedTags
* @returns {boolean}
*/
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);
};
/**
* @param {object} jsdoc
* @param {string} targetTagName
* @returns {boolean}
*/
const hasTag = (jsdoc, targetTagName) => {
const targetTagLower = targetTagName.toLowerCase();
return jsdoc.tags.some(doc => {
return doc.tag.toLowerCase() === targetTagLower;
});
};
/**
* @param {object} jsdoc
* @param {Array} targetTagNames
* @returns {boolean}
*/
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.
* @param {"jsdoc"|"closure"|"typescript"} mode
* @returns {boolean}
* true in case a defined type is declared; otherwise false.
*/
const hasDefinedTypeTag = (tag, mode) => {
// 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();
// Exit early if matching
if (tagType === 'undefined' || tagType === 'void') {
return false;
}
let parsedTypes;
try {
parsedTypes = (0, _jsdoccomment.tryParse)(tagType, mode === 'permissive' ? undefined : [mode]);
} catch {
// Ignore
}
if (
// We do not traverse deeply as it could be, e.g., `Promise<void>`
parsedTypes && parsedTypes.type === 'JsdocTypeUnion' && parsedTypes.elements.find(elem => {
return elem.type === 'JsdocTypeUndefined' || elem.type === 'JsdocTypeName' && elem.value === 'void';
})) {
return false;
}
// In any other case, a type is present
return true;
};
/**
* @param map
* @param tag
* @returns {Map}
*/
const ensureMap = (map, tag) => {
if (!map.has(tag)) {
map.set(tag, new Map());
}
return map.get(tag);
};
/**
* @param structuredTags
* @param tagMap
*/
const overrideTagStructure = (structuredTags, tagMap = tagStructure) => {
for (const [tag, {
name,
type,
required = []
}] of Object.entries(structuredTags)) {
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);
}
};
/**
* @param mode
* @param structuredTags
* @returns {Map}
*/
const getTagStructureForMode = (mode, structuredTags) => {
const tagStruct = (0, _getDefaultTagStructureForMode.default)(mode);
try {
overrideTagStructure(structuredTags, tagStruct);
} catch {
//
}
return tagStruct;
};
/**
* @param tag
* @param {Map} tagMap
* @returns {boolean}
*/
const isNamepathDefiningTag = (tag, tagMap = tagStructure) => {
const tagStruct = ensureMap(tagMap, tag);
return tagStruct.get('nameContents') === 'namepath-defining';
};
/**
* @param tag
* @param {Map} tagMap
* @returns {boolean}
*/
const tagMustHaveTypePosition = (tag, tagMap = tagStructure) => {
const tagStruct = ensureMap(tagMap, tag);
return tagStruct.get('typeRequired');
};
/**
* @param tag
* @param {Map} tagMap
* @returns {boolean}
*/
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']);
/**
* @param tag
* @param {Map} tagMap
* @returns {boolean}
*/
const tagMightHaveNamePosition = (tag, tagMap = tagStructure) => {
const tagStruct = ensureMap(tagMap, tag);
const ret = tagStruct.get('nameContents');
return ret === undefined ? true : Boolean(ret);
};
/**
* @param tag
* @param {Map} tagMap
* @returns {boolean}
*/
const tagMightHaveNamepath = (tag, tagMap = tagStructure) => {
const tagStruct = ensureMap(tagMap, tag);
return namepathTypes.has(tagStruct.get('nameContents'));
};
/**
* @param tag
* @param {Map} tagMap
* @returns {boolean}
*/
const tagMustHaveNamePosition = (tag, tagMap = tagStructure) => {
const tagStruct = ensureMap(tagMap, tag);
return tagStruct.get('nameRequired');
};
/**
* @param tag
* @param {Map} tagMap
* @returns {boolean}
*/
const tagMightHaveEitherTypeOrNamePosition = (tag, tagMap) => {
return tagMightHaveTypePosition(tag, tagMap) || tagMightHaveNamepath(tag, tagMap);
};
/**
* @param tag
* @param {Map} tagMap
* @returns {boolean}
*/
const tagMustHaveEitherTypeOrNamePosition = (tag, tagMap) => {
const tagStruct = ensureMap(tagMap, tag);
return tagStruct.get('typeOrNameRequired');
};
/**
* @param tag
* @param {Map} tagMap
* @returns {boolean}
*/
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;
};
// 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 'PropertyDefinition':
/* eslint-disable no-fallthrough */
// istanbul ignore next -- In Babel?
case 'ObjectProperty':
// 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<string>}
*/
const parseClosureTemplateTag = tag => {
return tag.name.split(',').map(type => {
return type.trim().replace(/^\[(?<name>.*?)=.*\]$/u, '$<name>');
});
};
/**
* @typedef {true|string[]} DefaultContexts
*/
/**
* 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 {DefaultContexts} defaultContexts
* @returns {string[]}
*/
const enforcedContexts = (context, defaultContexts) => {
const {
contexts = defaultContexts === true ? ['ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression', 'TSDeclareFunction'] : defaultContexts
} = context.options[0] || {};
return contexts;
};
/**
* @param {string[]} contexts
* @param {Function} checkJsdoc
* @param {Function} handler
*/
const getContextObject = (contexts, checkJsdoc, handler) => {
const properties = {};
for (const [idx, prop] of contexts.entries()) {
let property;
let value;
if (typeof prop === 'object') {
const selInfo = {
lastIndex: idx,
selector: prop.context
};
if (prop.comment) {
property = prop.context;
value = checkJsdoc.bind(null, {
...selInfo,
comment: prop.comment
}, handler.bind(null, prop.comment));
} else {
property = prop.context;
value = checkJsdoc.bind(null, selInfo, null);
}
} else {
const selInfo = {
lastIndex: idx,
selector: prop
};
property = prop;
value = checkJsdoc.bind(null, selInfo, null);
}
const old = properties[property];
properties[property] = old ? function (...args) {
old(...args);
value(...args);
} : value;
}
return properties;
};
const filterTags = (tags, filter) => {
return tags.filter(tag => {
return filter(tag);
});
};
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;
return (((_sourceCode$text$matc = sourceCode.text.match(/^\n*([ \t]+)/u)) === null || _sourceCode$text$matc === void 0 ? void 0 : _sourceCode$text$matc[1]) ?? '') + ' ';
};
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 => {
var _node$parent2;
return node && ((_node$parent2 = node.parent) === null || _node$parent2 === void 0 ? void 0 : _node$parent2.kind) === 'get';
};
const isSetter = node => {
var _node$parent3;
return node && ((_node$parent3 = node.parent) === null || _node$parent3 === void 0 ? void 0 : _node$parent3.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$;
const schemaProperties = schema[0].properties;
return ((_context$options$ = context.options[0]) === null || _context$options$ === void 0 ? void 0 : _context$options$[prop]) ?? (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');
};
/**
* @param {string} name
* @returns {(otherPathName: string) => void}
*/
const comparePaths = name => {
return otherPathName => {
return otherPathName === name || dropPathSegmentQuotes(otherPathName) === dropPathSegmentQuotes(name);
};
};
/**
* @param {string} name
* @param {string} otherPathName
* @returns {boolean}
*/
const pathDoesNotBeginWith = (name, otherPathName) => {
return !name.startsWith(otherPathName) && !dropPathSegmentQuotes(name).startsWith(dropPathSegmentQuotes(otherPathName));
};
/**
* @param {string} regexString
* @param {string} requiredFlags
* @returns {RegExp}
*/
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: _hasReturnValue.hasReturnValue,
hasTag,
hasThrowValue,
hasValueOrExecutorHasNonEmptyResolveValue: _hasReturnValue.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