|                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |  | /** * @fileoverview Validates JSDoc comments are syntactically correct * @author Nicholas C. Zakas * @deprecated in ESLint v5.10.0 */"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const doctrine = require("doctrine");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */module.exports = {    meta: {        type: "suggestion",
        docs: {            description: "Enforce valid JSDoc comments",            recommended: false,            url: "https://eslint.org/docs/latest/rules/valid-jsdoc"        },
        schema: [            {                type: "object",                properties: {                    prefer: {                        type: "object",                        additionalProperties: {                            type: "string"                        }                    },                    preferType: {                        type: "object",                        additionalProperties: {                            type: "string"                        }                    },                    requireReturn: {                        type: "boolean",                        default: true                    },                    requireParamDescription: {                        type: "boolean",                        default: true                    },                    requireReturnDescription: {                        type: "boolean",                        default: true                    },                    matchDescription: {                        type: "string"                    },                    requireReturnType: {                        type: "boolean",                        default: true                    },                    requireParamType: {                        type: "boolean",                        default: true                    }                },                additionalProperties: false            }        ],
        fixable: "code",        messages: {            unexpectedTag: "Unexpected @{{title}} tag; function has no return statement.",            expected: "Expected JSDoc for '{{name}}' but found '{{jsdocName}}'.",            use: "Use @{{name}} instead.",            useType: "Use '{{expectedTypeName}}' instead of '{{currentTypeName}}'.",            syntaxError: "JSDoc syntax error.",            missingBrace: "JSDoc type missing brace.",            missingParamDesc: "Missing JSDoc parameter description for '{{name}}'.",            missingParamType: "Missing JSDoc parameter type for '{{name}}'.",            missingReturnType: "Missing JSDoc return type.",            missingReturnDesc: "Missing JSDoc return description.",            missingReturn: "Missing JSDoc @{{returns}} for function.",            missingParam: "Missing JSDoc for parameter '{{name}}'.",            duplicateParam: "Duplicate JSDoc parameter '{{name}}'.",            unsatisfiedDesc: "JSDoc description does not satisfy the regex pattern."        },
        deprecated: true,        replacedBy: []    },
    create(context) {
        const options = context.options[0] || {},            prefer = options.prefer || {},            sourceCode = context.sourceCode,
            // these both default to true, so you have to explicitly make them false
            requireReturn = options.requireReturn !== false,            requireParamDescription = options.requireParamDescription !== false,            requireReturnDescription = options.requireReturnDescription !== false,            requireReturnType = options.requireReturnType !== false,            requireParamType = options.requireParamType !== false,            preferType = options.preferType || {},            checkPreferType = Object.keys(preferType).length !== 0;
        //--------------------------------------------------------------------------
        // Helpers
        //--------------------------------------------------------------------------
        // Using a stack to store if a function returns or not (handling nested functions)
        const fns = [];
        /**         * Check if node type is a Class         * @param {ASTNode} node node to check.         * @returns {boolean} True is its a class         * @private         */        function isTypeClass(node) {            return node.type === "ClassExpression" || node.type === "ClassDeclaration";        }
        /**         * When parsing a new function, store it in our function stack.         * @param {ASTNode} node A function node to check.         * @returns {void}         * @private         */        function startFunction(node) {            fns.push({                returnPresent: (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") ||                    isTypeClass(node) || node.async            });        }
        /**         * Indicate that return has been found in the current function.         * @param {ASTNode} node The return node.         * @returns {void}         * @private         */        function addReturn(node) {            const functionState = fns[fns.length - 1];
            if (functionState && node.argument !== null) {                functionState.returnPresent = true;            }        }
        /**         * Check if return tag type is void or undefined         * @param {Object} tag JSDoc tag         * @returns {boolean} True if its of type void or undefined         * @private         */        function isValidReturnType(tag) {            return tag.type === null || tag.type.name === "void" || tag.type.type === "UndefinedLiteral";        }
        /**         * Check if type should be validated based on some exceptions         * @param {Object} type JSDoc tag         * @returns {boolean} True if it can be validated         * @private         */        function canTypeBeValidated(type) {            return type !== "UndefinedLiteral" && // {undefined} as there is no name property available.
                   type !== "NullLiteral" && // {null}
                   type !== "NullableLiteral" && // {?}
                   type !== "FunctionType" && // {function(a)}
                   type !== "AllLiteral"; // {*}
        }
        /**         * Extract the current and expected type based on the input type object         * @param {Object} type JSDoc tag         * @returns {{currentType: Doctrine.Type, expectedTypeName: string}} The current type annotation and         * the expected name of the annotation         * @private         */        function getCurrentExpectedTypes(type) {            let currentType;
            if (type.name) {                currentType = type;            } else if (type.expression) {                currentType = type.expression;            }
            return {                currentType,                expectedTypeName: currentType && preferType[currentType.name]            };        }
        /**         * Gets the location of a JSDoc node in a file         * @param {Token} jsdocComment The comment that this node is parsed from         * @param {{range: number[]}} parsedJsdocNode A tag or other node which was parsed from this comment         * @returns {{start: SourceLocation, end: SourceLocation}} The 0-based source location for the tag         */        function getAbsoluteRange(jsdocComment, parsedJsdocNode) {            return {                start: sourceCode.getLocFromIndex(jsdocComment.range[0] + 2 + parsedJsdocNode.range[0]),                end: sourceCode.getLocFromIndex(jsdocComment.range[0] + 2 + parsedJsdocNode.range[1])            };        }
        /**         * Validate type for a given JSDoc node         * @param {Object} jsdocNode JSDoc node         * @param {Object} type JSDoc tag         * @returns {void}         * @private         */        function validateType(jsdocNode, type) {            if (!type || !canTypeBeValidated(type.type)) {                return;            }
            const typesToCheck = [];            let elements = [];
            switch (type.type) {                case "TypeApplication": // {Array.<String>}
                    elements = type.applications[0].type === "UnionType" ? type.applications[0].elements : type.applications;                    typesToCheck.push(getCurrentExpectedTypes(type));                    break;                case "RecordType": // {{20:String}}
                    elements = type.fields;                    break;                case "UnionType": // {String|number|Test}
                case "ArrayType": // {[String, number, Test]}
                    elements = type.elements;                    break;                case "FieldType": // Array.<{count: number, votes: number}>
                    if (type.value) {                        typesToCheck.push(getCurrentExpectedTypes(type.value));                    }                    break;                default:                    typesToCheck.push(getCurrentExpectedTypes(type));            }
            elements.forEach(validateType.bind(null, jsdocNode));
            typesToCheck.forEach(typeToCheck => {                if (typeToCheck.expectedTypeName &&                    typeToCheck.expectedTypeName !== typeToCheck.currentType.name) {                    context.report({                        node: jsdocNode,                        messageId: "useType",                        loc: getAbsoluteRange(jsdocNode, typeToCheck.currentType),                        data: {                            currentTypeName: typeToCheck.currentType.name,                            expectedTypeName: typeToCheck.expectedTypeName                        },                        fix(fixer) {                            return fixer.replaceTextRange(                                typeToCheck.currentType.range.map(indexInComment => jsdocNode.range[0] + 2 + indexInComment),                                typeToCheck.expectedTypeName                            );                        }                    });                }            });        }
        /**         * Validate the JSDoc node and output warnings if anything is wrong.         * @param {ASTNode} node The AST node to check.         * @returns {void}         * @private         */        function checkJSDoc(node) {            const jsdocNode = sourceCode.getJSDocComment(node),                functionData = fns.pop(),                paramTagsByName = Object.create(null),                paramTags = [];            let hasReturns = false,                returnsTag,                hasConstructor = false,                isInterface = false,                isOverride = false,                isAbstract = false;
            // make sure only to validate JSDoc comments
            if (jsdocNode) {                let jsdoc;
                try {                    jsdoc = doctrine.parse(jsdocNode.value, {                        strict: true,                        unwrap: true,                        sloppy: true,                        range: true                    });                } catch (ex) {
                    if (/braces/iu.test(ex.message)) {                        context.report({ node: jsdocNode, messageId: "missingBrace" });                    } else {                        context.report({ node: jsdocNode, messageId: "syntaxError" });                    }
                    return;                }
                jsdoc.tags.forEach(tag => {
                    switch (tag.title.toLowerCase()) {
                        case "param":                        case "arg":                        case "argument":                            paramTags.push(tag);                            break;
                        case "return":                        case "returns":                            hasReturns = true;                            returnsTag = tag;                            break;
                        case "constructor":                        case "class":                            hasConstructor = true;                            break;
                        case "override":                        case "inheritdoc":                            isOverride = true;                            break;
                        case "abstract":                        case "virtual":                            isAbstract = true;                            break;
                        case "interface":                            isInterface = true;                            break;
                        // no default
                    }
                    // check tag preferences
                    if (Object.prototype.hasOwnProperty.call(prefer, tag.title) && tag.title !== prefer[tag.title]) {                        const entireTagRange = getAbsoluteRange(jsdocNode, tag);
                        context.report({                            node: jsdocNode,                            messageId: "use",                            loc: {                                start: entireTagRange.start,                                end: {                                    line: entireTagRange.start.line,                                    column: entireTagRange.start.column + `@${tag.title}`.length                                }                            },                            data: { name: prefer[tag.title] },                            fix(fixer) {                                return fixer.replaceTextRange(                                    [                                        jsdocNode.range[0] + tag.range[0] + 3,                                        jsdocNode.range[0] + tag.range[0] + tag.title.length + 3                                    ],                                    prefer[tag.title]                                );                            }                        });                    }
                    // validate the types
                    if (checkPreferType && tag.type) {                        validateType(jsdocNode, tag.type);                    }                });
                paramTags.forEach(param => {                    if (requireParamType && !param.type) {                        context.report({                            node: jsdocNode,                            messageId: "missingParamType",                            loc: getAbsoluteRange(jsdocNode, param),                            data: { name: param.name }                        });                    }                    if (!param.description && requireParamDescription) {                        context.report({                            node: jsdocNode,                            messageId: "missingParamDesc",                            loc: getAbsoluteRange(jsdocNode, param),                            data: { name: param.name }                        });                    }                    if (paramTagsByName[param.name]) {                        context.report({                            node: jsdocNode,                            messageId: "duplicateParam",                            loc: getAbsoluteRange(jsdocNode, param),                            data: { name: param.name }                        });                    } else if (!param.name.includes(".")) {                        paramTagsByName[param.name] = param;                    }                });
                if (hasReturns) {                    if (!requireReturn && !functionData.returnPresent && (returnsTag.type === null || !isValidReturnType(returnsTag)) && !isAbstract) {                        context.report({                            node: jsdocNode,                            messageId: "unexpectedTag",                            loc: getAbsoluteRange(jsdocNode, returnsTag),                            data: {                                title: returnsTag.title                            }                        });                    } else {                        if (requireReturnType && !returnsTag.type) {                            context.report({ node: jsdocNode, messageId: "missingReturnType" });                        }
                        if (!isValidReturnType(returnsTag) && !returnsTag.description && requireReturnDescription) {                            context.report({ node: jsdocNode, messageId: "missingReturnDesc" });                        }                    }                }
                // check for functions missing @returns
                if (!isOverride && !hasReturns && !hasConstructor && !isInterface &&                    node.parent.kind !== "get" && node.parent.kind !== "constructor" &&                    node.parent.kind !== "set" && !isTypeClass(node)) {                    if (requireReturn || (functionData.returnPresent && !node.async)) {                        context.report({                            node: jsdocNode,                            messageId: "missingReturn",                            data: {                                returns: prefer.returns || "returns"                            }                        });                    }                }
                // check the parameters
                const jsdocParamNames = Object.keys(paramTagsByName);
                if (node.params) {                    node.params.forEach((param, paramsIndex) => {                        const bindingParam = param.type === "AssignmentPattern"                            ? param.left                            : param;
                        // TODO(nzakas): Figure out logical things to do with destructured, default, rest params
                        if (bindingParam.type === "Identifier") {                            const name = bindingParam.name;
                            if (jsdocParamNames[paramsIndex] && (name !== jsdocParamNames[paramsIndex])) {                                context.report({                                    node: jsdocNode,                                    messageId: "expected",                                    loc: getAbsoluteRange(jsdocNode, paramTagsByName[jsdocParamNames[paramsIndex]]),                                    data: {                                        name,                                        jsdocName: jsdocParamNames[paramsIndex]                                    }                                });                            } else if (!paramTagsByName[name] && !isOverride) {                                context.report({                                    node: jsdocNode,                                    messageId: "missingParam",                                    data: {                                        name                                    }                                });                            }                        }                    });                }
                if (options.matchDescription) {                    const regex = new RegExp(options.matchDescription, "u");
                    if (!regex.test(jsdoc.description)) {                        context.report({ node: jsdocNode, messageId: "unsatisfiedDesc" });                    }                }
            }
        }
        //--------------------------------------------------------------------------
        // Public
        //--------------------------------------------------------------------------
        return {            ArrowFunctionExpression: startFunction,            FunctionExpression: startFunction,            FunctionDeclaration: startFunction,            ClassExpression: startFunction,            ClassDeclaration: startFunction,            "ArrowFunctionExpression:exit": checkJSDoc,            "FunctionExpression:exit": checkJSDoc,            "FunctionDeclaration:exit": checkJSDoc,            "ClassExpression:exit": checkJSDoc,            "ClassDeclaration:exit": checkJSDoc,            ReturnStatement: addReturn        };
    }};
 |