1/** 2 * @fileoverview Rule that warns when identifier names are shorter or longer 3 * than the values provided in configuration. 4 * @author Burak Yigit Kaya aka BYK 5 */ 6 7"use strict"; 8 9//------------------------------------------------------------------------------ 10// Rule Definition 11//------------------------------------------------------------------------------ 12 13module.exports = { 14 meta: { 15 type: "suggestion", 16 17 docs: { 18 description: "enforce minimum and maximum identifier lengths", 19 category: "Stylistic Issues", 20 recommended: false, 21 url: "https://eslint.org/docs/rules/id-length" 22 }, 23 24 schema: [ 25 { 26 type: "object", 27 properties: { 28 min: { 29 type: "integer", 30 default: 2 31 }, 32 max: { 33 type: "integer" 34 }, 35 exceptions: { 36 type: "array", 37 uniqueItems: true, 38 items: { 39 type: "string" 40 } 41 }, 42 exceptionPatterns: { 43 type: "array", 44 uniqueItems: true, 45 items: { 46 type: "string" 47 } 48 }, 49 properties: { 50 enum: ["always", "never"] 51 } 52 }, 53 additionalProperties: false 54 } 55 ], 56 messages: { 57 tooShort: "Identifier name '{{name}}' is too short (< {{min}}).", 58 tooLong: "Identifier name '{{name}}' is too long (> {{max}})." 59 } 60 }, 61 62 create(context) { 63 const options = context.options[0] || {}; 64 const minLength = typeof options.min !== "undefined" ? options.min : 2; 65 const maxLength = typeof options.max !== "undefined" ? options.max : Infinity; 66 const properties = options.properties !== "never"; 67 const exceptions = new Set(options.exceptions); 68 const exceptionPatterns = (options.exceptionPatterns || []).map(pattern => new RegExp(pattern, "u")); 69 const reportedNode = new Set(); 70 71 /** 72 * Checks if a string matches the provided exception patterns 73 * @param {string} name The string to check. 74 * @returns {boolean} if the string is a match 75 * @private 76 */ 77 function matchesExceptionPattern(name) { 78 return exceptionPatterns.some(pattern => pattern.test(name)); 79 } 80 81 const SUPPORTED_EXPRESSIONS = { 82 MemberExpression: properties && function(parent) { 83 return !parent.computed && ( 84 85 // regular property assignment 86 (parent.parent.left === parent && parent.parent.type === "AssignmentExpression" || 87 88 // or the last identifier in an ObjectPattern destructuring 89 parent.parent.type === "Property" && parent.parent.value === parent && 90 parent.parent.parent.type === "ObjectPattern" && parent.parent.parent.parent.left === parent.parent.parent) 91 ); 92 }, 93 AssignmentPattern(parent, node) { 94 return parent.left === node; 95 }, 96 VariableDeclarator(parent, node) { 97 return parent.id === node; 98 }, 99 Property(parent, node) { 100 101 if (parent.parent.type === "ObjectPattern") { 102 return ( 103 parent.value !== parent.key && parent.value === node || 104 parent.value === parent.key && parent.key === node && properties 105 ); 106 } 107 return properties && !parent.computed && parent.key === node; 108 }, 109 ImportDefaultSpecifier: true, 110 RestElement: true, 111 FunctionExpression: true, 112 ArrowFunctionExpression: true, 113 ClassDeclaration: true, 114 FunctionDeclaration: true, 115 MethodDefinition: true, 116 CatchClause: true, 117 ArrayPattern: true 118 }; 119 120 return { 121 Identifier(node) { 122 const name = node.name; 123 const parent = node.parent; 124 125 const isShort = name.length < minLength; 126 const isLong = name.length > maxLength; 127 128 if (!(isShort || isLong) || exceptions.has(name) || matchesExceptionPattern(name)) { 129 return; // Nothing to report 130 } 131 132 const isValidExpression = SUPPORTED_EXPRESSIONS[parent.type]; 133 134 if (isValidExpression && !reportedNode.has(node) && (isValidExpression === true || isValidExpression(parent, node))) { 135 reportedNode.add(node); 136 context.report({ 137 node, 138 messageId: isShort ? "tooShort" : "tooLong", 139 data: { name, min: minLength, max: maxLength } 140 }); 141 } 142 } 143 }; 144 } 145}; 146