• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview A rule to disallow `this` keywords outside of classes or class-like objects.
3 * @author Toru Nagashima
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Requirements
10//------------------------------------------------------------------------------
11
12const astUtils = require("./utils/ast-utils");
13
14//------------------------------------------------------------------------------
15// Rule Definition
16//------------------------------------------------------------------------------
17
18module.exports = {
19    meta: {
20        type: "suggestion",
21
22        docs: {
23            description: "disallow `this` keywords outside of classes or class-like objects",
24            category: "Best Practices",
25            recommended: false,
26            url: "https://eslint.org/docs/rules/no-invalid-this"
27        },
28
29        schema: [
30            {
31                type: "object",
32                properties: {
33                    capIsConstructor: {
34                        type: "boolean",
35                        default: true
36                    }
37                },
38                additionalProperties: false
39            }
40        ],
41
42        messages: {
43            unexpectedThis: "Unexpected 'this'."
44        }
45    },
46
47    create(context) {
48        const options = context.options[0] || {};
49        const capIsConstructor = options.capIsConstructor !== false;
50        const stack = [],
51            sourceCode = context.getSourceCode();
52
53        /**
54         * Gets the current checking context.
55         *
56         * The return value has a flag that whether or not `this` keyword is valid.
57         * The flag is initialized when got at the first time.
58         * @returns {{valid: boolean}}
59         *   an object which has a flag that whether or not `this` keyword is valid.
60         */
61        stack.getCurrent = function() {
62            const current = this[this.length - 1];
63
64            if (!current.init) {
65                current.init = true;
66                current.valid = !astUtils.isDefaultThisBinding(
67                    current.node,
68                    sourceCode,
69                    { capIsConstructor }
70                );
71            }
72            return current;
73        };
74
75        /**
76         * Pushs new checking context into the stack.
77         *
78         * The checking context is not initialized yet.
79         * Because most functions don't have `this` keyword.
80         * When `this` keyword was found, the checking context is initialized.
81         * @param {ASTNode} node A function node that was entered.
82         * @returns {void}
83         */
84        function enterFunction(node) {
85
86            // `this` can be invalid only under strict mode.
87            stack.push({
88                init: !context.getScope().isStrict,
89                node,
90                valid: true
91            });
92        }
93
94        /**
95         * Pops the current checking context from the stack.
96         * @returns {void}
97         */
98        function exitFunction() {
99            stack.pop();
100        }
101
102        return {
103
104            /*
105             * `this` is invalid only under strict mode.
106             * Modules is always strict mode.
107             */
108            Program(node) {
109                const scope = context.getScope(),
110                    features = context.parserOptions.ecmaFeatures || {};
111
112                stack.push({
113                    init: true,
114                    node,
115                    valid: !(
116                        scope.isStrict ||
117                        node.sourceType === "module" ||
118                        (features.globalReturn && scope.childScopes[0].isStrict)
119                    )
120                });
121            },
122
123            "Program:exit"() {
124                stack.pop();
125            },
126
127            FunctionDeclaration: enterFunction,
128            "FunctionDeclaration:exit": exitFunction,
129            FunctionExpression: enterFunction,
130            "FunctionExpression:exit": exitFunction,
131
132            // Reports if `this` of the current context is invalid.
133            ThisExpression(node) {
134                const current = stack.getCurrent();
135
136                if (current && !current.valid) {
137                    context.report({
138                        node,
139                        messageId: "unexpectedThis"
140                    });
141                }
142            }
143        };
144    }
145};
146