blob: 884461a62b4a72dff112ebfb5fbbb73fff686981 [file] [log] [blame]
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _lodash = _interopRequireDefault(require("lodash"));
var _tagNames = require("./tagNames");
var _WarnSettings = _interopRequireDefault(require("./WarnSettings"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const getFunctionParameterNames = functionNode => {
const getParamName = param => {
if (_lodash.default.has(param, 'name')) {
return param.name;
}
if (_lodash.default.has(param, 'left.name')) {
return param.left.name;
}
if (param.type === 'ObjectPattern' || _lodash.default.get(param, 'left.type') === 'ObjectPattern') {
return '<ObjectPattern>';
}
if (param.type === 'ArrayPattern' || _lodash.default.get(param, 'left.type') === 'ArrayPattern') {
return '<ArrayPattern>';
}
if (param.type === 'RestElement') {
return param.argument.name;
}
if (param.type === 'TSParameterProperty') {
return getParamName(param.parameter);
}
throw new Error('Unsupported function signature format.');
};
return functionNode.params.map(getParamName);
};
/**
* 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) => {
return (jsdoc.tags || []).reduce((arr, {
name,
tag
}, idx) => {
if (tag !== targetTagName) {
return arr;
}
arr.push({
idx,
name
});
return arr;
}, []);
};
const getJsdocTags = (jsdoc, targetTagName) => {
let jsdocNames;
jsdocNames = getJsdocTagsDeep(jsdoc, targetTagName);
jsdocNames = jsdocNames.filter(({
name
}) => {
return !name.includes('.');
});
return jsdocNames;
};
const modeWarnSettings = (0, _WarnSettings.default)();
const getTagNamesForMode = (mode, context) => {
switch (mode) {
case 'jsdoc':
return _tagNames.jsdocTags;
case 'typescript':
return _tagNames.typeScriptTags;
case 'closure':
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 = {}) => {
const prefValues = _lodash.default.values(tagPreference);
if (prefValues.includes(name) || prefValues.some(prefVal => {
return prefVal && typeof prefVal === 'object' && prefVal.replacement === name;
})) {
return name;
}
if (_lodash.default.has(tagPreference, name)) {
return tagPreference[name];
}
const tagNames = getTagNamesForMode(mode, context);
const preferredTagName = _lodash.default.findKey(tagNames, aliases => {
return aliases.includes(name);
});
if (preferredTagName) {
return preferredTagName;
}
return name;
};
const isValidTag = (context, mode, name, definedTags) => {
const tagNames = getTagNamesForMode(mode, context);
const validTagNames = _lodash.default.keys(tagNames).concat(_lodash.default.flatten(_lodash.default.values(tagNames)));
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 return value.
*
* @param {JsDocTag} tag
* the tag which should be checked.
* @returns {boolean}
* true in case a return value is declared; otherwise false.
*/
const hasDefinedTypeReturnTag = tag => {
// The function should not continue in the event @returns is not defined...
if (typeof tag === 'undefined' || tag === null) {
return false;
} // .. same applies if it declares `@returns {undefined}` or `@returns {void}`
const tagType = tag.type.trim();
if (tagType === 'undefined' || tagType === 'void') {
return false;
} // In any other case, something must be returned, and
// a return statement is expected
return true;
};
const tagsWithMandatoryTypePosition = [// These both show curly brackets in the doc signature and examples
// "typeExpression"
'implements', // "typeName"
'type'];
const tagsWithMandatoryTypePositionClosure = [...tagsWithMandatoryTypePosition, 'this', 'define']; // All of these have a signature with "type" except for
// `augments`/`extends` ("namepath")
// `param`/`arg`/`argument` (no signature)
// `property`/`prop` (no signature)
// `modifies` (undocumented)
const tagsWithOptionalTypePosition = [// These have the example showing curly brackets but not in their doc signature, e.g.: https://jsdoc.app/tags-enum.html
'enum', 'member', 'var', 'typedef', // These do not show curly brackets in either the signature or examples
'augments', 'extends', 'class', 'constructor', 'constant', 'const', // These show the signature with curly brackets but not in the example
'module', 'namespace', // These have no formal signature in the docs but show curly brackets
// in the examples
'param', 'arg', 'argument', 'property', 'prop', // These show curly brackets in the signature and in the examples
'returns', 'return', 'throws', 'exception', 'yields', 'yield', // Has no documentation, but test example has curly brackets, and
// "name" would be suggested rather than "namepath" based on example; not
// sure if name is required
'modifies'];
const tagsWithOptionalTypePositionClosure = [...tagsWithOptionalTypePosition, 'export', // Shows the signature with curly brackets but not in the example
// "typeExpression"
'package', 'private', 'protected', // These do not show a signature nor show curly brackets in the example
'public', 'static']; // None of these show as having curly brackets for their name/namepath
const namepathDefiningTags = [// These appear to require a "name" in their signature, albeit these
// are somewhat different from other "name"'s (including as described
// at https://jsdoc.app/about-namepaths.html )
'external', 'host', 'event', // These allow for "name"'s in their signature, but indicate as optional
'class', 'constructor', 'constant', 'const', 'function', 'func', 'method', 'interface', 'member', 'var', 'mixin', 'namespace', // Todo: Should add `module` here (with optional "name" and no curly brackets);
// this block impacts `no-undefined-types` and `valid-types` (search for
// "isNamepathDefiningTag|tagMightHaveNamePosition|tagMightHaveEitherTypeOrNamePosition")
// These seem to all require a "namepath" in their signatures (with no counter-examples)
'name', 'typedef', 'callback']; // The following do not seem to allow curly brackets in their doc
// signature or examples (besides `modifies`)
const tagsWithOptionalNamePosition = [...namepathDefiningTags, // `borrows` has a different format, however, so needs special parsing;
// seems to require both, and as "namepath"'s
'borrows', // Signature seems to require a "name" (of an event) and no counter-examples
'emits', 'fires', 'listens', // Signature seems to require a "namepath" (and no counter-examples)
'alias', 'augments', 'extends', 'lends', 'this', // Signature seems to require a "namepath" (and no counter-examples),
// though it allows an incomplete namepath ending with connecting symbol
'memberof', 'memberof!', // Signature seems to require a "OtherObjectPath" with no counter-examples
'mixes', // Signature allows for "namepath" or text
'see']; // Todo: `@link` seems to require a namepath OR URL and might be checked as such.
// The doc signature of `event` seems to require a "name"
const tagsWithMandatoryNamePosition = [// "name" (and a special syntax for the `external` name)
'external', 'host', // "namepath"
'callback', 'name', 'typedef'];
const tagsWithMandatoryTypeOrNamePosition = [// "namepath"
'alias', 'augments', 'extends', 'borrows', 'lends', 'memberof', 'memberof!', 'name', 'this', 'typedef', // "name"
'external', 'host', // "OtherObjectPath"
'mixes'];
const isNamepathDefiningTag = tagName => {
return namepathDefiningTags.includes(tagName);
};
const tagMightHaveTypePosition = (mode, tag) => {
if (mode === 'closure') {
return tagsWithMandatoryTypePositionClosure.includes(tag) || tagsWithOptionalTypePositionClosure.includes(tag);
}
return tagsWithMandatoryTypePosition.includes(tag) || tagsWithOptionalTypePosition.includes(tag);
};
const tagMustHaveTypePosition = (mode, tag) => {
if (mode === 'closure') {
return tagsWithMandatoryTypePositionClosure.includes(tag);
}
return tagsWithMandatoryTypePosition.includes(tag);
};
const tagMightHaveNamePosition = tag => {
return tagsWithOptionalNamePosition.includes(tag);
};
const tagMustHaveNamePosition = tag => {
return tagsWithMandatoryNamePosition.includes(tag);
};
const tagMightHaveEitherTypeOrNamePosition = (mode, tag) => {
return tagMightHaveTypePosition(mode, tag) || tagMightHaveNamePosition(tag);
};
const tagMustHaveEitherTypeOrNamePosition = tag => {
return tagsWithMandatoryTypeOrNamePosition.includes(tag);
};
/**
* Checks if a node has a return statement. Void return does not count.
*
* @param {object} node
* @returns {boolean}
*/
// eslint-disable-next-line complexity
const hasReturnValue = node => {
if (!node) {
return false;
}
switch (node.type) {
case 'FunctionExpression':
case 'FunctionDeclaration':
case 'ArrowFunctionExpression':
{
return node.expression || hasReturnValue(node.body);
}
case 'BlockStatement':
{
return node.body.some(bodyNode => {
return bodyNode.type !== 'FunctionDeclaration' && hasReturnValue(bodyNode);
});
}
case 'WhileStatement':
case 'DoWhileStatement':
case 'ForStatement':
case 'ForInStatement':
case 'ForOfStatement':
case 'WithStatement':
{
return hasReturnValue(node.body);
}
case 'IfStatement':
{
return hasReturnValue(node.consequent) || hasReturnValue(node.alternate);
}
case 'TryStatement':
{
return hasReturnValue(node.block) || hasReturnValue(node.handler && node.handler.body) || hasReturnValue(node.finalizer);
}
case 'SwitchStatement':
{
return node.cases.some(someCase => {
return someCase.consequent.some(hasReturnValue);
});
}
case 'ReturnStatement':
{
// void return does not count.
if (node.argument === null) {
return false;
}
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}
* @param {JsDocTag} tag
* @returns {Array<string>}
*/
const parseClosureTemplateTag = tag => {
return (tag.name + ' ' + tag.description).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 _ref = context.options[0] || {},
_ref$contexts = _ref.contexts,
contexts = _ref$contexts === void 0 ? defaultContexts === true ? ['ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression'] : defaultContexts : _ref$contexts;
return contexts;
};
/**
* @param {string[]} contexts
* @param {Function} checkJsdoc
*/
const getContextObject = (contexts, checkJsdoc) => {
return contexts.reduce((obj, prop) => {
obj[prop] = checkJsdoc;
return obj;
}, {});
};
const filterTags = (tags = [], filter) => {
return tags.filter(filter);
};
const tagsWithNamesAndDescriptions = ['param', 'arg', 'argument', 'property', 'prop', // 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 tagName = tag.tag;
const tagWithName = tagsWithNamesAndDescriptions.includes(tagName);
if (!tagWithName && tagName !== descName) {
tagsWithoutNames.push(tag);
}
return tagWithName;
});
return {
tagsWithNames,
tagsWithoutNames
};
};
const getIndent = sourceCode => {
let indent = sourceCode.text.match(/^\n*([ \t]+)/u);
/* istanbul ignore next */
indent = indent ? indent[1] + indent[1].charAt() : ' ';
return indent;
};
var _default = {
enforcedContexts,
filterTags,
getContextObject,
getFunctionParameterNames,
getIndent,
getJsdocTags,
getJsdocTagsDeep,
getPreferredTagName,
getTagsByType,
hasATag,
hasDefinedTypeReturnTag,
hasReturnValue,
hasTag,
isNamepathDefiningTag,
isValidTag,
parseClosureTemplateTag,
tagMightHaveEitherTypeOrNamePosition,
tagMightHaveNamePosition,
tagMightHaveTypePosition,
tagMustHaveEitherTypeOrNamePosition,
tagMustHaveNamePosition,
tagMustHaveTypePosition
};
exports.default = _default;
module.exports = exports.default;
//# sourceMappingURL=jsdocUtils.js.map