Skip to content

Commit a8db9a5

Browse files
authored
fix: no-invalid-this false positive in class field initializer (#15495)
* fix: no-invalid-this false positive in class field initializer Fixes #15477 * fix jsdoc
1 parent 19ad061 commit a8db9a5

File tree

2 files changed

+92
-53
lines changed

2 files changed

+92
-53
lines changed

lib/rules/no-invalid-this.js

Lines changed: 50 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,21 @@
1111

1212
const astUtils = require("./utils/ast-utils");
1313

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+
1429
//------------------------------------------------------------------------------
1530
// Rule Definition
1631
//------------------------------------------------------------------------------
@@ -72,71 +87,53 @@ module.exports = {
7287
return current;
7388
};
7489

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 {
9391

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+
}
10196

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+
});
103110

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+
}
111113

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+
*/
112122
stack.push({
113-
init: true,
123+
init: !context.getScope().isStrict,
114124
node,
115-
valid: !(
116-
scope.isStrict ||
117-
node.sourceType === "module" ||
118-
(features.globalReturn && scope.childScopes[0].isStrict)
119-
)
125+
valid: true
120126
});
121127
},
122128

123-
"Program:exit"() {
129+
onCodePathEnd(codePath, node) {
130+
if (isCodePathWithLexicalThis(codePath, node)) {
131+
return;
132+
}
133+
124134
stack.pop();
125135
},
126136

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-
140137
// Reports if `this` of the current context is invalid.
141138
ThisExpression(node) {
142139
const current = stack.getCurrent();

tests/lib/rules/no-invalid-this.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,15 @@ const patterns = [
119119
valid: [NORMAL],
120120
invalid: [USE_STRICT, IMPLIED_STRICT, MODULES]
121121
},
122+
{
123+
code: "() => { this }; this;",
124+
parserOptions: {
125+
ecmaVersion: 6
126+
},
127+
errors,
128+
valid: [NORMAL],
129+
invalid: [USE_STRICT, IMPLIED_STRICT, MODULES]
130+
},
122131

123132
// IIFE.
124133
{
@@ -745,6 +754,18 @@ const patterns = [
745754
},
746755

747756
// Class fields.
757+
{
758+
code: "class C { field = this }",
759+
parserOptions: { ecmaVersion: 2022 },
760+
valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
761+
invalid: []
762+
},
763+
{
764+
code: "class C { static field = this }",
765+
parserOptions: { ecmaVersion: 2022 },
766+
valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
767+
invalid: []
768+
},
748769
{
749770
code: "class C { field = console.log(this); }",
750771
parserOptions: { ecmaVersion: 2022 },
@@ -776,6 +797,20 @@ const patterns = [
776797
invalid: [USE_STRICT, IMPLIED_STRICT, MODULES],
777798
errors: [{ messageId: "unexpectedThis", type: "ThisExpression" }]
778799
},
800+
{
801+
code: "class C { foo = () => this; }",
802+
parserOptions: { ecmaVersion: 2022 },
803+
valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
804+
invalid: [],
805+
errors: [{ messageId: "unexpectedThis", type: "ThisExpression" }]
806+
},
807+
{
808+
code: "class C { foo = () => { this }; }",
809+
parserOptions: { ecmaVersion: 2022 },
810+
valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
811+
invalid: [],
812+
errors: [{ messageId: "unexpectedThis", type: "ThisExpression" }]
813+
},
779814

780815
// Class static blocks
781816
{
@@ -817,6 +852,13 @@ const patterns = [
817852
invalid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
818853
errors: [{ messageId: "unexpectedThis", type: "ThisExpression" }]
819854
},
855+
{
856+
code: "class C { static {} [this]; }",
857+
parserOptions: { ecmaVersion: 2022 },
858+
valid: [NORMAL],
859+
invalid: [USE_STRICT, IMPLIED_STRICT, MODULES],
860+
errors: [{ messageId: "unexpectedThis", type: "ThisExpression" }]
861+
},
820862
{
821863
code: "class C { static {} [this.x]; }",
822864
parserOptions: { ecmaVersion: 2022 },

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy