|                                                                                                                                                                                                                                                                                                                                                                                                               |  | /** * @fileoverview Rule to flag non-camelcased identifiers * @author Nicholas C. Zakas */
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */module.exports = {    meta: {        type: "suggestion",
        docs: {            description: "Enforce camelcase naming convention",            recommended: false,            url: "https://eslint.org/docs/latest/rules/camelcase"        },
        schema: [            {                type: "object",                properties: {                    ignoreDestructuring: {                        type: "boolean",                        default: false                    },                    ignoreImports: {                        type: "boolean",                        default: false                    },                    ignoreGlobals: {                        type: "boolean",                        default: false                    },                    properties: {                        enum: ["always", "never"]                    },                    allow: {                        type: "array",                        items: [                            {                                type: "string"                            }                        ],                        minItems: 0,                        uniqueItems: true                    }                },                additionalProperties: false            }        ],
        messages: {            notCamelCase: "Identifier '{{name}}' is not in camel case.",            notCamelCasePrivate: "#{{name}} is not in camel case."        }    },
    create(context) {        const options = context.options[0] || {};        const properties = options.properties === "never" ? "never" : "always";        const ignoreDestructuring = options.ignoreDestructuring;        const ignoreImports = options.ignoreImports;        const ignoreGlobals = options.ignoreGlobals;        const allow = options.allow || [];        const sourceCode = context.sourceCode;
        //--------------------------------------------------------------------------
        // Helpers
        //--------------------------------------------------------------------------
        // contains reported nodes to avoid reporting twice on destructuring with shorthand notation
        const reported = new Set();
        /**         * Checks if a string contains an underscore and isn't all upper-case         * @param {string} name The string to check.         * @returns {boolean} if the string is underscored         * @private         */        function isUnderscored(name) {            const nameBody = name.replace(/^_+|_+$/gu, "");
            // if there's an underscore, it might be A_CONSTANT, which is okay
            return nameBody.includes("_") && nameBody !== nameBody.toUpperCase();        }
        /**         * Checks if a string match the ignore list         * @param {string} name The string to check.         * @returns {boolean} if the string is ignored         * @private         */        function isAllowed(name) {            return allow.some(                entry => name === entry || name.match(new RegExp(entry, "u"))            );        }
        /**         * Checks if a given name is good or not.         * @param {string} name The name to check.         * @returns {boolean} `true` if the name is good.         * @private         */        function isGoodName(name) {            return !isUnderscored(name) || isAllowed(name);        }
        /**         * Checks if a given identifier reference or member expression is an assignment         * target.         * @param {ASTNode} node The node to check.         * @returns {boolean} `true` if the node is an assignment target.         */        function isAssignmentTarget(node) {            const parent = node.parent;
            switch (parent.type) {                case "AssignmentExpression":                case "AssignmentPattern":                    return parent.left === node;
                case "Property":                    return (                        parent.parent.type === "ObjectPattern" &&                        parent.value === node                    );                case "ArrayPattern":                case "RestElement":                    return true;
                default:                    return false;            }        }
        /**         * Checks if a given binding identifier uses the original name as-is.         * - If it's in object destructuring or object expression, the original name is its property name.         * - If it's in import declaration, the original name is its exported name.         * @param {ASTNode} node The `Identifier` node to check.         * @returns {boolean} `true` if the identifier uses the original name as-is.         */        function equalsToOriginalName(node) {            const localName = node.name;            const valueNode = node.parent.type === "AssignmentPattern"                ? node.parent                : node;            const parent = valueNode.parent;
            switch (parent.type) {                case "Property":                    return (                        (parent.parent.type === "ObjectPattern" || parent.parent.type === "ObjectExpression") &&                        parent.value === valueNode &&                        !parent.computed &&                        parent.key.type === "Identifier" &&                        parent.key.name === localName                    );
                case "ImportSpecifier":                    return (                        parent.local === node &&                        astUtils.getModuleExportName(parent.imported) === localName                    );
                default:                    return false;            }        }
        /**         * Reports an AST node as a rule violation.         * @param {ASTNode} node The node to report.         * @returns {void}         * @private         */        function report(node) {            if (reported.has(node.range[0])) {                return;            }            reported.add(node.range[0]);
            // Report it.
            context.report({                node,                messageId: node.type === "PrivateIdentifier"                    ? "notCamelCasePrivate"                    : "notCamelCase",                data: { name: node.name }            });        }
        /**         * Reports an identifier reference or a binding identifier.         * @param {ASTNode} node The `Identifier` node to report.         * @returns {void}         */        function reportReferenceId(node) {
            /*             * For backward compatibility, if it's in callings then ignore it.             * Not sure why it is.             */            if (                node.parent.type === "CallExpression" ||                node.parent.type === "NewExpression"            ) {                return;            }
            /*             * For backward compatibility, if it's a default value of             * destructuring/parameters then ignore it.             * Not sure why it is.             */            if (                node.parent.type === "AssignmentPattern" &&                node.parent.right === node            ) {                return;            }
            /*             * The `ignoreDestructuring` flag skips the identifiers that uses             * the property name as-is.             */            if (ignoreDestructuring && equalsToOriginalName(node)) {                return;            }
            report(node);        }
        return {
            // Report camelcase of global variable references ------------------
            Program(node) {                const scope = sourceCode.getScope(node);
                if (!ignoreGlobals) {
                    // Defined globals in config files or directive comments.
                    for (const variable of scope.variables) {                        if (                            variable.identifiers.length > 0 ||                            isGoodName(variable.name)                        ) {                            continue;                        }                        for (const reference of variable.references) {
                            /*                             * For backward compatibility, this rule reports read-only                             * references as well.                             */                            reportReferenceId(reference.identifier);                        }                    }                }
                // Undefined globals.
                for (const reference of scope.through) {                    const id = reference.identifier;
                    if (isGoodName(id.name)) {                        continue;                    }
                    /*                     * For backward compatibility, this rule reports read-only                     * references as well.                     */                    reportReferenceId(id);                }            },
            // Report camelcase of declared variables --------------------------
            [[                "VariableDeclaration",                "FunctionDeclaration",                "FunctionExpression",                "ArrowFunctionExpression",                "ClassDeclaration",                "ClassExpression",                "CatchClause"            ]](node) {                for (const variable of sourceCode.getDeclaredVariables(node)) {                    if (isGoodName(variable.name)) {                        continue;                    }                    const id = variable.identifiers[0];
                    // Report declaration.
                    if (!(ignoreDestructuring && equalsToOriginalName(id))) {                        report(id);                    }
                    /*                     * For backward compatibility, report references as well.                     * It looks unnecessary because declarations are reported.                     */                    for (const reference of variable.references) {                        if (reference.init) {                            continue; // Skip the write references of initializers.
                        }                        reportReferenceId(reference.identifier);                    }                }            },
            // Report camelcase in properties ----------------------------------
            [[                "ObjectExpression > Property[computed!=true] > Identifier.key",                "MethodDefinition[computed!=true] > Identifier.key",                "PropertyDefinition[computed!=true] > Identifier.key",                "MethodDefinition > PrivateIdentifier.key",                "PropertyDefinition > PrivateIdentifier.key"            ]](node) {                if (properties === "never" || isGoodName(node.name)) {                    return;                }                report(node);            },            "MemberExpression[computed!=true] > Identifier.property"(node) {                if (                    properties === "never" ||                    !isAssignmentTarget(node.parent) || // ← ignore read-only references.
                    isGoodName(node.name)                ) {                    return;                }                report(node);            },
            // Report camelcase in import --------------------------------------
            ImportDeclaration(node) {                for (const variable of sourceCode.getDeclaredVariables(node)) {                    if (isGoodName(variable.name)) {                        continue;                    }                    const id = variable.identifiers[0];
                    // Report declaration.
                    if (!(ignoreImports && equalsToOriginalName(id))) {                        report(id);                    }
                    /*                     * For backward compatibility, report references as well.                     * It looks unnecessary because declarations are reported.                     */                    for (const reference of variable.references) {                        reportReferenceId(reference.identifier);                    }                }            },
            // Report camelcase in re-export -----------------------------------
            [[                "ExportAllDeclaration > Identifier.exported",                "ExportSpecifier > Identifier.exported"            ]](node) {                if (isGoodName(node.name)) {                    return;                }                report(node);            },
            // Report camelcase in labels --------------------------------------
            [[                "LabeledStatement > Identifier.label",
                /*                 * For backward compatibility, report references as well.                 * It looks unnecessary because declarations are reported.                 */                "BreakStatement > Identifier.label",                "ContinueStatement > Identifier.label"            ]](node) {                if (isGoodName(node.name)) {                    return;                }                report(node);            }        };    }};
 |