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