--- /dev/null
+/**
+ * @fileoverview Rule to enforce consistent naming of "this" context variables
+ * @author Raphael Pigulla
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ type: "suggestion",
+
+ docs: {
+ description: "enforce consistent naming when capturing the current execution context",
+ category: "Stylistic Issues",
+ recommended: false,
+ url: "https://eslint.org/docs/rules/consistent-this"
+ },
+
+ schema: {
+ type: "array",
+ items: {
+ type: "string",
+ minLength: 1
+ },
+ uniqueItems: true
+ },
+
+ messages: {
+ aliasNotAssignedToThis: "Designated alias '{{name}}' is not assigned to 'this'.",
+ unexpectedAlias: "Unexpected alias '{{name}}' for 'this'."
+ }
+ },
+
+ create(context) {
+ let aliases = [];
+
+ if (context.options.length === 0) {
+ aliases.push("that");
+ } else {
+ aliases = context.options;
+ }
+
+ /**
+ * Reports that a variable declarator or assignment expression is assigning
+ * a non-'this' value to the specified alias.
+ * @param {ASTNode} node The assigning node.
+ * @param {string} name the name of the alias that was incorrectly used.
+ * @returns {void}
+ */
+ function reportBadAssignment(node, name) {
+ context.report({ node, messageId: "aliasNotAssignedToThis", data: { name } });
+ }
+
+ /**
+ * Checks that an assignment to an identifier only assigns 'this' to the
+ * appropriate alias, and the alias is only assigned to 'this'.
+ * @param {ASTNode} node The assigning node.
+ * @param {Identifier} name The name of the variable assigned to.
+ * @param {Expression} value The value of the assignment.
+ * @returns {void}
+ */
+ function checkAssignment(node, name, value) {
+ const isThis = value.type === "ThisExpression";
+
+ if (aliases.indexOf(name) !== -1) {
+ if (!isThis || node.operator && node.operator !== "=") {
+ reportBadAssignment(node, name);
+ }
+ } else if (isThis) {
+ context.report({ node, messageId: "unexpectedAlias", data: { name } });
+ }
+ }
+
+ /**
+ * Ensures that a variable declaration of the alias in a program or function
+ * is assigned to the correct value.
+ * @param {string} alias alias the check the assignment of.
+ * @param {Object} scope scope of the current code we are checking.
+ * @private
+ * @returns {void}
+ */
+ function checkWasAssigned(alias, scope) {
+ const variable = scope.set.get(alias);
+
+ if (!variable) {
+ return;
+ }
+
+ if (variable.defs.some(def => def.node.type === "VariableDeclarator" &&
+ def.node.init !== null)) {
+ return;
+ }
+
+ /*
+ * The alias has been declared and not assigned: check it was
+ * assigned later in the same scope.
+ */
+ if (!variable.references.some(reference => {
+ const write = reference.writeExpr;
+
+ return (
+ reference.from === scope &&
+ write && write.type === "ThisExpression" &&
+ write.parent.operator === "="
+ );
+ })) {
+ variable.defs.map(def => def.node).forEach(node => {
+ reportBadAssignment(node, alias);
+ });
+ }
+ }
+
+ /**
+ * Check each alias to ensure that is was assinged to the correct value.
+ * @returns {void}
+ */
+ function ensureWasAssigned() {
+ const scope = context.getScope();
+
+ aliases.forEach(alias => {
+ checkWasAssigned(alias, scope);
+ });
+ }
+
+ return {
+ "Program:exit": ensureWasAssigned,
+ "FunctionExpression:exit": ensureWasAssigned,
+ "FunctionDeclaration:exit": ensureWasAssigned,
+
+ VariableDeclarator(node) {
+ const id = node.id;
+ const isDestructuring =
+ id.type === "ArrayPattern" || id.type === "ObjectPattern";
+
+ if (node.init !== null && !isDestructuring) {
+ checkAssignment(node, id.name, node.init);
+ }
+ },
+
+ AssignmentExpression(node) {
+ if (node.left.type === "Identifier") {
+ checkAssignment(node, node.left.name, node.right);
+ }
+ }
+ };
+
+ }
+};