1// Copyright 2016 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4/* eslint-disable */ 5 6/** 7 * @fileoverview Rule to flag non-camelcased identifiers 8 * @author Nicholas C. Zakas 9 */ 10 11'use strict'; 12 13//------------------------------------------------------------------------------ 14// Rule Definition 15//------------------------------------------------------------------------------ 16 17module.exports = { 18 meta: { 19 docs: { 20 description: "enforce Catapult camelcase naming convention", 21 category: "Stylistic Issues", 22 recommended: false 23 }, 24 25 schema: [ 26 { 27 type: "object", 28 properties: { 29 properties: { 30 enum: ["always", "never"] 31 } 32 }, 33 additionalProperties: false 34 } 35 ] 36 }, 37 38 create(context) { 39 40 //-------------------------------------------------------------------------- 41 // Helpers 42 //-------------------------------------------------------------------------- 43 44 // contains reported nodes to avoid reporting twice on destructuring with shorthand notation 45 var reported = []; 46 47 /** 48 * Checks if a string contains an underscore and isn't all upper-case 49 * @param {string} name The string to check. 50 * @returns {boolean} if the string is underscored 51 * @private 52 */ 53 function isUnderscored(name) { 54 55 // if there's an underscore, it might be A_VARANT, which is okay 56 return name.indexOf("_") > -1 && name !== name.toUpperCase(); 57 } 58 59 /** 60 * Reports an AST node as a rule violation. 61 * @param {ASTNode} node The node to report. 62 * @returns {void} 63 * @private 64 */ 65 function report(node) { 66 if (reported.indexOf(node) < 0) { 67 reported.push(node); 68 context.report(node, "Identifier '{{name}}' is not in camel case.", { name: node.name }); 69 } 70 } 71 72 var options = context.options[0] || {}; 73 let properties = options.properties || ""; 74 75 if (properties !== "always" && properties !== "never") { 76 properties = "always"; 77 } 78 79 return { 80 81 Identifier(node) { 82 83 /* 84 * Leading and trailing underscores are commonly used to flag 85 * private/protected identifiers, strip them. 86 * 87 * NOTE: This has four Catapult-specific style exceptions: 88 * 89 * - The prefix opt_ 90 * - The prefix g_ 91 * - The suffix _smallerIsBetter 92 * - The suffix _biggerIsBetter 93 */ 94 var name = node.name.replace(/(?:^opt_)|^(?:^g_)|^_+|_+$|(?:_smallerIsBetter)$|(?:_biggerIsBetter)$/g, ""), 95 effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent; 96 97 // MemberExpressions get special rules 98 if (node.parent.type === "MemberExpression") { 99 100 // "never" check properties 101 if (properties === "never") { 102 return; 103 } 104 105 // Always report underscored object names 106 if (node.parent.object.type === "Identifier" && 107 node.parent.object.name === node.name && 108 isUnderscored(name)) { 109 report(node); 110 111 // Report AssignmentExpressions only if they are the left side of the assignment 112 } else if (effectiveParent.type === "AssignmentExpression" && 113 isUnderscored(name) && 114 (effectiveParent.right.type !== "MemberExpression" || 115 effectiveParent.left.type === "MemberExpression" && 116 effectiveParent.left.property.name === node.name)) { 117 report(node); 118 } 119 120 // Properties have their own rules 121 } else if (node.parent.type === "Property") { 122 123 // "never" check properties 124 if (properties === "never") { 125 return; 126 } 127 128 if (node.parent.parent && node.parent.parent.type === "ObjectPattern" && 129 node.parent.key === node && node.parent.value !== node) { 130 return; 131 } 132 133 if (isUnderscored(name) && effectiveParent.type !== "CallExpression") { 134 report(node); 135 } 136 137 // Check if it's an import specifier 138 } else if (["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"].indexOf(node.parent.type) >= 0) { 139 140 // Report only if the local imported identifier is underscored 141 if (node.parent.local && node.parent.local.name === node.name && isUnderscored(name)) { 142 report(node); 143 } 144 145 // Report anything that is underscored that isn't a CallExpression 146 } else if (isUnderscored(name) && effectiveParent.type !== "CallExpression") { 147 report(node); 148 } 149 } 150 151 }; 152 153 } 154}; 155