1/** 2 * @fileoverview Rule to enforce consistent naming of "this" context variables 3 * @author Raphael Pigulla 4 */ 5"use strict"; 6 7//------------------------------------------------------------------------------ 8// Rule Definition 9//------------------------------------------------------------------------------ 10 11module.exports = { 12 meta: { 13 type: "suggestion", 14 15 docs: { 16 description: "enforce consistent naming when capturing the current execution context", 17 category: "Stylistic Issues", 18 recommended: false, 19 url: "https://eslint.org/docs/rules/consistent-this" 20 }, 21 22 schema: { 23 type: "array", 24 items: { 25 type: "string", 26 minLength: 1 27 }, 28 uniqueItems: true 29 }, 30 31 messages: { 32 aliasNotAssignedToThis: "Designated alias '{{name}}' is not assigned to 'this'.", 33 unexpectedAlias: "Unexpected alias '{{name}}' for 'this'." 34 } 35 }, 36 37 create(context) { 38 let aliases = []; 39 40 if (context.options.length === 0) { 41 aliases.push("that"); 42 } else { 43 aliases = context.options; 44 } 45 46 /** 47 * Reports that a variable declarator or assignment expression is assigning 48 * a non-'this' value to the specified alias. 49 * @param {ASTNode} node The assigning node. 50 * @param {string} name the name of the alias that was incorrectly used. 51 * @returns {void} 52 */ 53 function reportBadAssignment(node, name) { 54 context.report({ node, messageId: "aliasNotAssignedToThis", data: { name } }); 55 } 56 57 /** 58 * Checks that an assignment to an identifier only assigns 'this' to the 59 * appropriate alias, and the alias is only assigned to 'this'. 60 * @param {ASTNode} node The assigning node. 61 * @param {Identifier} name The name of the variable assigned to. 62 * @param {Expression} value The value of the assignment. 63 * @returns {void} 64 */ 65 function checkAssignment(node, name, value) { 66 const isThis = value.type === "ThisExpression"; 67 68 if (aliases.indexOf(name) !== -1) { 69 if (!isThis || node.operator && node.operator !== "=") { 70 reportBadAssignment(node, name); 71 } 72 } else if (isThis) { 73 context.report({ node, messageId: "unexpectedAlias", data: { name } }); 74 } 75 } 76 77 /** 78 * Ensures that a variable declaration of the alias in a program or function 79 * is assigned to the correct value. 80 * @param {string} alias alias the check the assignment of. 81 * @param {Object} scope scope of the current code we are checking. 82 * @private 83 * @returns {void} 84 */ 85 function checkWasAssigned(alias, scope) { 86 const variable = scope.set.get(alias); 87 88 if (!variable) { 89 return; 90 } 91 92 if (variable.defs.some(def => def.node.type === "VariableDeclarator" && 93 def.node.init !== null)) { 94 return; 95 } 96 97 /* 98 * The alias has been declared and not assigned: check it was 99 * assigned later in the same scope. 100 */ 101 if (!variable.references.some(reference => { 102 const write = reference.writeExpr; 103 104 return ( 105 reference.from === scope && 106 write && write.type === "ThisExpression" && 107 write.parent.operator === "=" 108 ); 109 })) { 110 variable.defs.map(def => def.node).forEach(node => { 111 reportBadAssignment(node, alias); 112 }); 113 } 114 } 115 116 /** 117 * Check each alias to ensure that is was assigned to the correct value. 118 * @returns {void} 119 */ 120 function ensureWasAssigned() { 121 const scope = context.getScope(); 122 123 aliases.forEach(alias => { 124 checkWasAssigned(alias, scope); 125 }); 126 } 127 128 return { 129 "Program:exit": ensureWasAssigned, 130 "FunctionExpression:exit": ensureWasAssigned, 131 "FunctionDeclaration:exit": ensureWasAssigned, 132 133 VariableDeclarator(node) { 134 const id = node.id; 135 const isDestructuring = 136 id.type === "ArrayPattern" || id.type === "ObjectPattern"; 137 138 if (node.init !== null && !isDestructuring) { 139 checkAssignment(node, id.name, node.init); 140 } 141 }, 142 143 AssignmentExpression(node) { 144 if (node.left.type === "Identifier") { 145 checkAssignment(node, node.left.name, node.right); 146 } 147 } 148 }; 149 150 } 151}; 152