• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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