| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 | /** * @fileoverview Rule that warns when identifier names are shorter or longer * than the values provided in configuration. * @author Burak Yigit Kaya aka BYK */"use strict";//------------------------------------------------------------------------------// Requirements//------------------------------------------------------------------------------const GraphemeSplitter = require("grapheme-splitter");//------------------------------------------------------------------------------// Helpers//------------------------------------------------------------------------------/** * Checks if the string given as argument is ASCII or not. * @param {string} value A string that you want to know if it is ASCII or not. * @returns {boolean} `true` if `value` is ASCII string. */function isASCII(value) {    if (typeof value !== "string") {        return false;    }    return /^[\u0020-\u007f]*$/u.test(value);}/** @type {GraphemeSplitter | undefined} */let splitter;/** * Gets the length of the string. If the string is not in ASCII, counts graphemes. * @param {string} value A string that you want to get the length. * @returns {number} The length of `value`. */function getStringLength(value) {    if (isASCII(value)) {        return value.length;    }    if (!splitter) {        splitter = new GraphemeSplitter();    }    return splitter.countGraphemes(value);}//------------------------------------------------------------------------------// Rule Definition//------------------------------------------------------------------------------/** @type {import('../shared/types').Rule} */module.exports = {    meta: {        type: "suggestion",        docs: {            description: "Enforce minimum and maximum identifier lengths",            recommended: false,            url: "https://eslint.org/docs/rules/id-length"        },        schema: [            {                type: "object",                properties: {                    min: {                        type: "integer",                        default: 2                    },                    max: {                        type: "integer"                    },                    exceptions: {                        type: "array",                        uniqueItems: true,                        items: {                            type: "string"                        }                    },                    exceptionPatterns: {                        type: "array",                        uniqueItems: true,                        items: {                            type: "string"                        }                    },                    properties: {                        enum: ["always", "never"]                    }                },                additionalProperties: false            }        ],        messages: {            tooShort: "Identifier name '{{name}}' is too short (< {{min}}).",            tooShortPrivate: "Identifier name '#{{name}}' is too short (< {{min}}).",            tooLong: "Identifier name '{{name}}' is too long (> {{max}}).",            tooLongPrivate: "Identifier name #'{{name}}' is too long (> {{max}})."        }    },    create(context) {        const options = context.options[0] || {};        const minLength = typeof options.min !== "undefined" ? options.min : 2;        const maxLength = typeof options.max !== "undefined" ? options.max : Infinity;        const properties = options.properties !== "never";        const exceptions = new Set(options.exceptions);        const exceptionPatterns = (options.exceptionPatterns || []).map(pattern => new RegExp(pattern, "u"));        const reportedNodes = new Set();        /**         * Checks if a string matches the provided exception patterns         * @param {string} name The string to check.         * @returns {boolean} if the string is a match         * @private         */        function matchesExceptionPattern(name) {            return exceptionPatterns.some(pattern => pattern.test(name));        }        const SUPPORTED_EXPRESSIONS = {            MemberExpression: properties && function(parent) {                return !parent.computed && (                    // regular property assignment                    (parent.parent.left === parent && parent.parent.type === "AssignmentExpression" ||                    // or the last identifier in an ObjectPattern destructuring                    parent.parent.type === "Property" && parent.parent.value === parent &&                    parent.parent.parent.type === "ObjectPattern" && parent.parent.parent.parent.left === parent.parent.parent)                );            },            AssignmentPattern(parent, node) {                return parent.left === node;            },            VariableDeclarator(parent, node) {                return parent.id === node;            },            Property(parent, node) {                if (parent.parent.type === "ObjectPattern") {                    const isKeyAndValueSame = parent.value.name === parent.key.name;                    return (                        !isKeyAndValueSame && parent.value === node ||                        isKeyAndValueSame && parent.key === node && properties                    );                }                return properties && !parent.computed && parent.key.name === node.name;            },            ImportDefaultSpecifier: true,            RestElement: true,            FunctionExpression: true,            ArrowFunctionExpression: true,            ClassDeclaration: true,            FunctionDeclaration: true,            MethodDefinition: true,            PropertyDefinition: true,            CatchClause: true,            ArrayPattern: true        };        return {            [[                "Identifier",                "PrivateIdentifier"            ]](node) {                const name = node.name;                const parent = node.parent;                const nameLength = getStringLength(name);                const isShort = nameLength < minLength;                const isLong = nameLength > maxLength;                if (!(isShort || isLong) || exceptions.has(name) || matchesExceptionPattern(name)) {                    return; // Nothing to report                }                const isValidExpression = SUPPORTED_EXPRESSIONS[parent.type];                /*                 * We used the range instead of the node because it's possible                 * for the same identifier to be represented by two different                 * nodes, with the most clear example being shorthand properties:                 * { foo }                 * In this case, "foo" is represented by one node for the name                 * and one for the value. The only way to know they are the same                 * is to look at the range.                 */                if (isValidExpression && !reportedNodes.has(node.range.toString()) && (isValidExpression === true || isValidExpression(parent, node))) {                    reportedNodes.add(node.range.toString());                    let messageId = isShort ? "tooShort" : "tooLong";                    if (node.type === "PrivateIdentifier") {                        messageId += "Private";                    }                    context.report({                        node,                        messageId,                        data: { name, min: minLength, max: maxLength }                    });                }            }        };    }};
 |