]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/no-invalid-this.js
import 8.23.1 source
[pve-eslint.git] / eslint / lib / rules / no-invalid-this.js
1 /**
2 * @fileoverview A rule to disallow `this` keywords in contexts where the value of `this` is `undefined`.
3 * @author Toru Nagashima
4 */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Requirements
10 //------------------------------------------------------------------------------
11
12 const astUtils = require("./utils/ast-utils");
13
14 //------------------------------------------------------------------------------
15 // Helpers
16 //------------------------------------------------------------------------------
17
18 /**
19 * Determines if the given code path is a code path with lexical `this` binding.
20 * That is, if `this` within the code path refers to `this` of surrounding code path.
21 * @param {CodePath} codePath Code path.
22 * @param {ASTNode} node Node that started the code path.
23 * @returns {boolean} `true` if it is a code path with lexical `this` binding.
24 */
25 function isCodePathWithLexicalThis(codePath, node) {
26 return codePath.origin === "function" && node.type === "ArrowFunctionExpression";
27 }
28
29 //------------------------------------------------------------------------------
30 // Rule Definition
31 //------------------------------------------------------------------------------
32
33 /** @type {import('../shared/types').Rule} */
34 module.exports = {
35 meta: {
36 type: "suggestion",
37
38 docs: {
39 description: "Disallow use of `this` in contexts where the value of `this` is `undefined`",
40 recommended: false,
41 url: "https://eslint.org/docs/rules/no-invalid-this"
42 },
43
44 schema: [
45 {
46 type: "object",
47 properties: {
48 capIsConstructor: {
49 type: "boolean",
50 default: true
51 }
52 },
53 additionalProperties: false
54 }
55 ],
56
57 messages: {
58 unexpectedThis: "Unexpected 'this'."
59 }
60 },
61
62 create(context) {
63 const options = context.options[0] || {};
64 const capIsConstructor = options.capIsConstructor !== false;
65 const stack = [],
66 sourceCode = context.getSourceCode();
67
68 /**
69 * Gets the current checking context.
70 *
71 * The return value has a flag that whether or not `this` keyword is valid.
72 * The flag is initialized when got at the first time.
73 * @returns {{valid: boolean}}
74 * an object which has a flag that whether or not `this` keyword is valid.
75 */
76 stack.getCurrent = function() {
77 const current = this[this.length - 1];
78
79 if (!current.init) {
80 current.init = true;
81 current.valid = !astUtils.isDefaultThisBinding(
82 current.node,
83 sourceCode,
84 { capIsConstructor }
85 );
86 }
87 return current;
88 };
89
90 return {
91
92 onCodePathStart(codePath, node) {
93 if (isCodePathWithLexicalThis(codePath, node)) {
94 return;
95 }
96
97 if (codePath.origin === "program") {
98 const scope = context.getScope();
99 const features = context.parserOptions.ecmaFeatures || {};
100
101 // `this` at the top level of scripts always refers to the global object
102 stack.push({
103 init: true,
104 node,
105 valid: !(
106 node.sourceType === "module" ||
107 (features.globalReturn && scope.childScopes[0].isStrict)
108 )
109 });
110
111 return;
112 }
113
114 /*
115 * `init: false` means that `valid` isn't determined yet.
116 * Most functions don't use `this`, and the calculation for `valid`
117 * is relatively costly, so we'll calculate it lazily when the first
118 * `this` within the function is traversed. A special case are non-strict
119 * functions, because `this` refers to the global object and therefore is
120 * always valid, so we can set `init: true` right away.
121 */
122 stack.push({
123 init: !context.getScope().isStrict,
124 node,
125 valid: true
126 });
127 },
128
129 onCodePathEnd(codePath, node) {
130 if (isCodePathWithLexicalThis(codePath, node)) {
131 return;
132 }
133
134 stack.pop();
135 },
136
137 // Reports if `this` of the current context is invalid.
138 ThisExpression(node) {
139 const current = stack.getCurrent();
140
141 if (current && !current.valid) {
142 context.report({
143 node,
144 messageId: "unexpectedThis"
145 });
146 }
147 }
148 };
149 }
150 };