|
11 | 11 |
|
12 | 12 | const astUtils = require("./utils/ast-utils");
|
13 | 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 | + |
14 | 29 | //------------------------------------------------------------------------------
|
15 | 30 | // Rule Definition
|
16 | 31 | //------------------------------------------------------------------------------
|
@@ -72,71 +87,53 @@ module.exports = {
|
72 | 87 | return current;
|
73 | 88 | };
|
74 | 89 |
|
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 |
| - } |
| 90 | + return { |
93 | 91 |
|
94 |
| - /** |
95 |
| - * Pops the current checking context from the stack. |
96 |
| - * @returns {void} |
97 |
| - */ |
98 |
| - function exitFunction() { |
99 |
| - stack.pop(); |
100 |
| - } |
| 92 | + onCodePathStart(codePath, node) { |
| 93 | + if (isCodePathWithLexicalThis(codePath, node)) { |
| 94 | + return; |
| 95 | + } |
101 | 96 |
|
102 |
| - return { |
| 97 | + if (codePath.origin === "program") { |
| 98 | + const scope = context.getScope(); |
| 99 | + const features = context.parserOptions.ecmaFeatures || {}; |
| 100 | + |
| 101 | + stack.push({ |
| 102 | + init: true, |
| 103 | + node, |
| 104 | + valid: !( |
| 105 | + scope.isStrict || |
| 106 | + node.sourceType === "module" || |
| 107 | + (features.globalReturn && scope.childScopes[0].isStrict) |
| 108 | + ) |
| 109 | + }); |
103 | 110 |
|
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 | + return; |
| 112 | + } |
111 | 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 | + */ |
112 | 122 | stack.push({
|
113 |
| - init: true, |
| 123 | + init: !context.getScope().isStrict, |
114 | 124 | node,
|
115 |
| - valid: !( |
116 |
| - scope.isStrict || |
117 |
| - node.sourceType === "module" || |
118 |
| - (features.globalReturn && scope.childScopes[0].isStrict) |
119 |
| - ) |
| 125 | + valid: true |
120 | 126 | });
|
121 | 127 | },
|
122 | 128 |
|
123 |
| - "Program:exit"() { |
| 129 | + onCodePathEnd(codePath, node) { |
| 130 | + if (isCodePathWithLexicalThis(codePath, node)) { |
| 131 | + return; |
| 132 | + } |
| 133 | + |
124 | 134 | stack.pop();
|
125 | 135 | },
|
126 | 136 |
|
127 |
| - FunctionDeclaration: enterFunction, |
128 |
| - "FunctionDeclaration:exit": exitFunction, |
129 |
| - FunctionExpression: enterFunction, |
130 |
| - "FunctionExpression:exit": exitFunction, |
131 |
| - |
132 |
| - // Field initializers are implicit functions. |
133 |
| - "PropertyDefinition > *.value": enterFunction, |
134 |
| - "PropertyDefinition > *.value:exit": exitFunction, |
135 |
| - |
136 |
| - // Class static blocks are implicit functions. |
137 |
| - StaticBlock: enterFunction, |
138 |
| - "StaticBlock:exit": exitFunction, |
139 |
| - |
140 | 137 | // Reports if `this` of the current context is invalid.
|
141 | 138 | ThisExpression(node) {
|
142 | 139 | const current = stack.getCurrent();
|
|
0 commit comments