| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297 | /** * @author Toru Nagashima * See LICENSE file in root directory for full license. */"use strict"/*istanbul ignore next *//** * This function is copied from https://github.com/eslint/eslint/blob/2355f8d0de1d6732605420d15ddd4f1eee3c37b6/lib/ast-utils.js#L648-L684 * * @param {ASTNode} node - The node to get. * @returns {string|null} The property name if static. Otherwise, null. * @private */function getStaticPropertyName(node) {    let prop = null    switch (node && node.type) {        case "Property":        case "MethodDefinition":            prop = node.key            break        case "MemberExpression":            prop = node.property            break        // no default    }    switch (prop && prop.type) {        case "Literal":            return String(prop.value)        case "TemplateLiteral":            if (prop.expressions.length === 0 && prop.quasis.length === 1) {                return prop.quasis[0].value.cooked            }            break        case "Identifier":            if (!node.computed) {                return prop.name            }            break        // no default    }    return null}/** * Checks whether the given node is assignee or not. * * @param {ASTNode} node - The node to check. * @returns {boolean} `true` if the node is assignee. */function isAssignee(node) {    return (        node.parent.type === "AssignmentExpression" && node.parent.left === node    )}/** * Gets the top assignment expression node if the given node is an assignee. * * This is used to distinguish 2 assignees belong to the same assignment. * If the node is not an assignee, this returns null. * * @param {ASTNode} leafNode - The node to get. * @returns {ASTNode|null} The top assignment expression node, or null. */function getTopAssignment(leafNode) {    let node = leafNode    // Skip MemberExpressions.    while (        node.parent.type === "MemberExpression" &&        node.parent.object === node    ) {        node = node.parent    }    // Check assignments.    if (!isAssignee(node)) {        return null    }    // Find the top.    while (node.parent.type === "AssignmentExpression") {        node = node.parent    }    return node}/** * Gets top assignment nodes of the given node list. * * @param {ASTNode[]} nodes - The node list to get. * @returns {ASTNode[]} Gotten top assignment nodes. */function createAssignmentList(nodes) {    return nodes.map(getTopAssignment).filter(Boolean)}/** * Gets the reference of `module.exports` from the given scope. * * @param {escope.Scope} scope - The scope to get. * @returns {ASTNode[]} Gotten MemberExpression node list. */function getModuleExportsNodes(scope) {    const variable = scope.set.get("module")    if (variable == null) {        return []    }    return variable.references        .map(reference => reference.identifier.parent)        .filter(            node =>                node.type === "MemberExpression" &&                getStaticPropertyName(node) === "exports"        )}/** * Gets the reference of `exports` from the given scope. * * @param {escope.Scope} scope - The scope to get. * @returns {ASTNode[]} Gotten Identifier node list. */function getExportsNodes(scope) {    const variable = scope.set.get("exports")    if (variable == null) {        return []    }    return variable.references.map(reference => reference.identifier)}module.exports = {    meta: {        docs: {            description: "enforce either `module.exports` or `exports`",            category: "Stylistic Issues",            recommended: false,            url:                "https://github.com/mysticatea/eslint-plugin-node/blob/v8.0.1/docs/rules/exports-style.md",        },        type: "suggestion",        fixable: null,        schema: [            {                //                enum: ["module.exports", "exports"],            },            {                type: "object",                properties: { allowBatchAssign: { type: "boolean" } },                additionalProperties: false,            },        ],    },    create(context) {        const mode = context.options[0] || "module.exports"        const batchAssignAllowed = Boolean(            context.options[1] != null && context.options[1].allowBatchAssign        )        const sourceCode = context.getSourceCode()        /**         * Gets the location info of reports.         *         * exports = foo         * ^^^^^^^^^         *         * module.exports = foo         * ^^^^^^^^^^^^^^^^         *         * @param {ASTNode} node - The node of `exports`/`module.exports`.         * @returns {Location} The location info of reports.         */        function getLocation(node) {            const token = sourceCode.getTokenAfter(node)            return {                start: node.loc.start,                end: token.loc.end,            }        }        /**         * Enforces `module.exports`.         * This warns references of `exports`.         *         * @returns {void}         */        function enforceModuleExports() {            const globalScope = context.getScope()            const exportsNodes = getExportsNodes(globalScope)            const assignList = batchAssignAllowed                ? createAssignmentList(getModuleExportsNodes(globalScope))                : []            for (const node of exportsNodes) {                // Skip if it's a batch assignment.                if (                    assignList.length > 0 &&                    assignList.indexOf(getTopAssignment(node)) !== -1                ) {                    continue                }                // Report.                context.report({                    node,                    loc: getLocation(node),                    message:                        "Unexpected access to 'exports'. Use 'module.exports' instead.",                })            }        }        /**         * Enforces `exports`.         * This warns references of `module.exports`.         *         * @returns {void}         */        function enforceExports() {            const globalScope = context.getScope()            const exportsNodes = getExportsNodes(globalScope)            const moduleExportsNodes = getModuleExportsNodes(globalScope)            const assignList = batchAssignAllowed                ? createAssignmentList(exportsNodes)                : []            const batchAssignList = []            for (const node of moduleExportsNodes) {                // Skip if it's a batch assignment.                if (assignList.length > 0) {                    const found = assignList.indexOf(getTopAssignment(node))                    if (found !== -1) {                        batchAssignList.push(assignList[found])                        assignList.splice(found, 1)                        continue                    }                }                // Report.                context.report({                    node,                    loc: getLocation(node),                    message:                        "Unexpected access to 'module.exports'. Use 'exports' instead.",                })            }            // Disallow direct assignment to `exports`.            for (const node of exportsNodes) {                // Skip if it's not assignee.                if (!isAssignee(node)) {                    continue                }                // Check if it's a batch assignment.                if (batchAssignList.indexOf(getTopAssignment(node)) !== -1) {                    continue                }                // Report.                context.report({                    node,                    loc: getLocation(node),                    message:                        "Unexpected assignment to 'exports'. Don't modify 'exports' itself.",                })            }        }        return {            "Program:exit"() {                switch (mode) {                    case "module.exports":                        enforceModuleExports()                        break                    case "exports":                        enforceExports()                        break                    // no default                }            },        }    },}
 |