diff --git a/docs/rules/no-eval.md b/docs/rules/no-eval.md index 9f7b71ad620..4a2a3b79733 100644 --- a/docs/rules/no-eval.md +++ b/docs/rules/no-eval.md @@ -66,6 +66,14 @@ class A { eval() { } + + static { + // This is a user-defined static method. + this.eval("var a = 0"); + } + + static eval() { + } } ``` diff --git a/docs/rules/no-invalid-this.md b/docs/rules/no-invalid-this.md index 7614b59a87b..f2f17d41a95 100644 --- a/docs/rules/no-invalid-this.md +++ b/docs/rules/no-invalid-this.md @@ -18,7 +18,7 @@ This rule judges from following conditions whether or not the function is a meth * The function is on an object literal. * The function is assigned to a property. -* The function is a method/getter/setter of ES2015 Classes. (excepts static methods) +* The function is a method/getter/setter of ES2015 Classes. And this rule allows `this` keywords in functions below: @@ -26,6 +26,11 @@ And this rule allows `this` keywords in functions below: * The function is a callback of array methods (such as `.forEach()`) if `thisArg` is given. * The function has `@this` tag in its JSDoc comment. +And this rule always allows `this` keywords in the following contexts: + +* In class field initializers. +* In class static blocks. + Otherwise are considered problems. This rule applies **only** in strict mode. @@ -166,6 +171,13 @@ Foo.prototype.foo = function foo() { }; class Foo { + + // OK, this is in a class field initializer. + a = this.b; + + // OK, static initializers also have valid this. + static a = this.b; + foo() { // OK, this is in a method. this.a = 0; @@ -177,6 +189,12 @@ class Foo { this.a = 0; baz(() => this); } + + static { + // OK, static blocks also have valid this. + this.a = 0; + baz(() => this); + } } var foo = (function foo() { diff --git a/lib/rules/no-eval.js b/lib/rules/no-eval.js index 97481528357..96b85a07120 100644 --- a/lib/rules/no-eval.js +++ b/lib/rules/no-eval.js @@ -248,6 +248,8 @@ module.exports = { "ArrowFunctionExpression:exit": exitVarScope, "PropertyDefinition > *.value": enterVarScope, "PropertyDefinition > *.value:exit": exitVarScope, + StaticBlock: enterVarScope, + "StaticBlock:exit": exitVarScope, ThisExpression(node) { if (!isMember(node.parent, "eval")) { diff --git a/lib/rules/no-invalid-this.js b/lib/rules/no-invalid-this.js index 77558b90dcc..e1d7cbcf553 100644 --- a/lib/rules/no-invalid-this.js +++ b/lib/rules/no-invalid-this.js @@ -132,6 +132,10 @@ module.exports = { "PropertyDefinition > *.value": enterFunction, "PropertyDefinition > *.value:exit": exitFunction, + // Class static blocks are implicit functions. + StaticBlock: enterFunction, + "StaticBlock:exit": exitFunction, + // Reports if `this` of the current context is invalid. ThisExpression(node) { const current = stack.getCurrent(); diff --git a/lib/rules/utils/ast-utils.js b/lib/rules/utils/ast-utils.js index e4fd977c116..1d02d8a753a 100644 --- a/lib/rules/utils/ast-utils.js +++ b/lib/rules/utils/ast-utils.js @@ -937,6 +937,8 @@ module.exports = { * * First, this checks the node: * + * - The given node is not in `PropertyDefinition#value` position. + * - The given node is not `StaticBlock`. * - The function name does not start with uppercase. It's a convention to capitalize the names * of constructor functions. This check is not performed if `capIsConstructor` is set to `false`. * - The function does not have a JSDoc comment that has a @this tag. @@ -951,7 +953,8 @@ module.exports = { * - The location is not on an ES2015 class. * - Its `bind`/`call`/`apply` method is not called directly. * - The function is not a callback of array methods (such as `.forEach()`) if `thisArg` is given. - * @param {ASTNode} node A function node to check. + * @param {ASTNode} node A function node to check. It also can be an implicit function, like `StaticBlock` + * or any expression that is `PropertyDefinition#value` node. * @param {SourceCode} sourceCode A SourceCode instance to get comments. * @param {boolean} [capIsConstructor = true] `false` disables the assumption that functions which name starts * with an uppercase or are assigned to a variable which name starts with an uppercase are constructors. @@ -964,7 +967,12 @@ module.exports = { * Therefore, A expression node at `PropertyDefinition#value` is a function. * In this case, `this` is always not default binding. */ - if (node && node.parent && node.parent.type === "PropertyDefinition" && node.value === node) { + if (node.parent.type === "PropertyDefinition" && node.parent.value === node) { + return false; + } + + // Class static blocks are implicit functions. In this case, `this` is always not default binding. + if (node.type === "StaticBlock") { return false; } diff --git a/tests/lib/rules/no-eval.js b/tests/lib/rules/no-eval.js index 2f6f7f925fb..79ba4a1eb11 100644 --- a/tests/lib/rules/no-eval.js +++ b/tests/lib/rules/no-eval.js @@ -50,6 +50,7 @@ ruleTester.run("no-eval", rule, { { code: "class A { static foo() { this.eval(); } }", parserOptions: { ecmaVersion: 6 } }, { code: "class A { field = this.eval(); }", parserOptions: { ecmaVersion: 2022 } }, { code: "class A { field = () => this.eval(); }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { static { this.eval(); } }", parserOptions: { ecmaVersion: 2022 } }, // Allows indirect eval { code: "(0, eval)('foo')", options: [{ allowIndirect: true }] }, @@ -132,6 +133,12 @@ ruleTester.run("no-eval", rule, { code: "class C { [this.eval('foo')] }", parserOptions: { ecmaVersion: 2022 }, errors: [{ messageId: "unexpected" }] + }, + + { + code: "class A { static {} [this.eval()]; }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpected" }] } ] }); diff --git a/tests/lib/rules/no-invalid-this.js b/tests/lib/rules/no-invalid-this.js index 80e4c9c3a2b..62033fd389e 100644 --- a/tests/lib/rules/no-invalid-this.js +++ b/tests/lib/rules/no-invalid-this.js @@ -775,6 +775,54 @@ const patterns = [ valid: [NORMAL], // the global this in non-strict mode is OK. invalid: [USE_STRICT, IMPLIED_STRICT, MODULES], errors: [{ messageId: "unexpectedThis", type: "ThisExpression" }] + }, + + // Class static blocks + { + code: "class C { static { this.x; } }", + parserOptions: { ecmaVersion: 2022 }, + valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], + invalid: [] + }, + { + code: "class C { static { () => { this.x; } } }", + parserOptions: { ecmaVersion: 2022 }, + valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], + invalid: [] + }, + { + code: "class C { static { class D { [this.x]; } } }", + parserOptions: { ecmaVersion: 2022 }, + valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], + invalid: [] + }, + { + code: "class C { static { function foo() { this.x; } } }", + parserOptions: { ecmaVersion: 2022 }, + valid: [], + invalid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], + errors: [{ messageId: "unexpectedThis", type: "ThisExpression" }] + }, + { + code: "class C { static { (function() { this.x; }); } }", + parserOptions: { ecmaVersion: 2022 }, + valid: [], + invalid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], + errors: [{ messageId: "unexpectedThis", type: "ThisExpression" }] + }, + { + code: "class C { static { (function() { this.x; })(); } }", + parserOptions: { ecmaVersion: 2022 }, + valid: [], + invalid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], + errors: [{ messageId: "unexpectedThis", type: "ThisExpression" }] + }, + { + code: "class C { static {} [this.x]; }", + parserOptions: { ecmaVersion: 2022 }, + valid: [NORMAL], + invalid: [USE_STRICT, IMPLIED_STRICT, MODULES], + errors: [{ messageId: "unexpectedThis", type: "ThisExpression" }] } ];