Skip to content

Commit 14a4739

Browse files
Update: no-new-func rule catching eval case of MemberExpression (#14860)
* Fix: no-new-fuc should catch calls to Function.apply * Chore: adjust ES6 syntax in no-new-func * Docs: examples for incorrect no-new-func rule * Chore: tests for invalid no-new-func cases * Docs: example and test for incorrect no-new-func rule * Docs: enhance documentation for no-new-func rule * Docs: improve no-new-func description Co-authored-by: Milos Djermanovic <[email protected]> * Fix: revise no-new-func logic Co-authored-by: Milos Djermanovic <[email protected]> * Chore: additional test for Function.bind * Docs: note regarding Function.bind without immediate call * Fix: report CallExpression node instead of MemberExpression node * Docs: modify no-new-func docs Co-authored-by: Milos Djermanovic <[email protected]> * Docs: add no-new-func example Co-authored-by: Milos Djermanovic <[email protected]> * Fix: use maybeCallee instead of parent in no-new-func Co-authored-by: Milos Djermanovic <[email protected]> * add tests * check method name Co-authored-by: Milos Djermanovic <[email protected]>
1 parent 7f2346b commit 14a4739

File tree

3 files changed

+92
-9
lines changed

3 files changed

+92
-9
lines changed

docs/rules/no-new-func.md

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
# Disallow Function Constructor (no-new-func)
22

3-
It's possible to create functions in JavaScript using the `Function` constructor, such as:
3+
It's possible to create functions in JavaScript from strings at runtime using the `Function` constructor, such as:
44

55
```js
66
var x = new Function("a", "b", "return a + b");
7+
var x = Function("a", "b", "return a + b");
8+
var x = Function.call(null, "a", "b", "return a + b");
9+
var x = Function.apply(null, ["a", "b", "return a + b"]);
10+
var x = Function.bind(null, "a", "b", "return a + b")();
711
```
812

9-
This is considered by many to be a bad practice due to the difficulty in debugging and reading these types of functions.
13+
This is considered by many to be a bad practice due to the difficulty in debugging and reading these types of functions. In addition, Content-Security-Policy (CSP) directives may disallow the use of eval() and similar methods for creating code from strings.
1014

1115
## Rule Details
1216

@@ -19,6 +23,10 @@ Examples of **incorrect** code for this rule:
1923

2024
var x = new Function("a", "b", "return a + b");
2125
var x = Function("a", "b", "return a + b");
26+
var x = Function.call(null, "a", "b", "return a + b");
27+
var x = Function.apply(null, ["a", "b", "return a + b"]);
28+
var x = Function.bind(null, "a", "b", "return a + b")();
29+
var f = Function.bind(null, "a", "b", "return a + b"); // assuming that the result of Function.bind(...) will be eventually called.
2230
```
2331

2432
Examples of **correct** code for this rule:

lib/rules/no-new-func.js

+34-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@
55

66
"use strict";
77

8+
//------------------------------------------------------------------------------
9+
// Requirements
10+
//------------------------------------------------------------------------------
11+
12+
const astUtils = require("./utils/ast-utils");
13+
14+
//------------------------------------------------------------------------------
15+
// Helpers
16+
//------------------------------------------------------------------------------
17+
18+
const callMethods = new Set(["apply", "bind", "call"]);
19+
820
//------------------------------------------------------------------------------
921
// Rule Definition
1022
//------------------------------------------------------------------------------
@@ -37,14 +49,30 @@ module.exports = {
3749
variable.references.forEach(ref => {
3850
const node = ref.identifier;
3951
const { parent } = node;
52+
let evalNode;
53+
54+
if (parent) {
55+
if (node === parent.callee && (
56+
parent.type === "NewExpression" ||
57+
parent.type === "CallExpression"
58+
)) {
59+
evalNode = parent;
60+
} else if (
61+
parent.type === "MemberExpression" &&
62+
node === parent.object &&
63+
callMethods.has(astUtils.getStaticPropertyName(parent))
64+
) {
65+
const maybeCallee = parent.parent.type === "ChainExpression" ? parent.parent : parent;
66+
67+
if (maybeCallee.parent.type === "CallExpression" && maybeCallee.parent.callee === maybeCallee) {
68+
evalNode = maybeCallee.parent;
69+
}
70+
}
71+
}
4072

41-
if (
42-
parent &&
43-
(parent.type === "NewExpression" || parent.type === "CallExpression") &&
44-
node === parent.callee
45-
) {
73+
if (evalNode) {
4674
context.report({
47-
node: parent,
75+
node: evalNode,
4876
messageId: "noFunctionConstructor"
4977
});
5078
}

tests/lib/rules/no-new-func.js

+48-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,11 @@ ruleTester.run("no-new-func", rule, {
3838
"var fn = function () { function Function() {}; Function() }",
3939
"var x = function Function() { Function(); }",
4040
"call(Function)",
41-
"new Class(Function)"
41+
"new Class(Function)",
42+
"foo[Function]()",
43+
"foo(Function.bind)",
44+
"Function.toString()",
45+
"Function[call]()"
4246
],
4347
invalid: [
4448
{
@@ -55,6 +59,49 @@ ruleTester.run("no-new-func", rule, {
5559
type: "CallExpression"
5660
}]
5761
},
62+
{
63+
code: "var a = Function.call(null, \"b\", \"c\", \"return b+c\");",
64+
errors: [{
65+
messageId: "noFunctionConstructor",
66+
type: "CallExpression"
67+
}]
68+
},
69+
{
70+
code: "var a = Function.apply(null, [\"b\", \"c\", \"return b+c\"]);",
71+
errors: [{
72+
messageId: "noFunctionConstructor",
73+
type: "CallExpression"
74+
}]
75+
},
76+
{
77+
code: "var a = Function.bind(null, \"b\", \"c\", \"return b+c\")();",
78+
errors: [{
79+
messageId: "noFunctionConstructor",
80+
type: "CallExpression"
81+
}]
82+
},
83+
{
84+
code: "var a = Function.bind(null, \"b\", \"c\", \"return b+c\");",
85+
errors: [{
86+
messageId: "noFunctionConstructor",
87+
type: "CallExpression"
88+
}]
89+
},
90+
{
91+
code: "var a = Function[\"call\"](null, \"b\", \"c\", \"return b+c\");",
92+
errors: [{
93+
messageId: "noFunctionConstructor",
94+
type: "CallExpression"
95+
}]
96+
},
97+
{
98+
code: "var a = (Function?.call)(null, \"b\", \"c\", \"return b+c\");",
99+
parserOptions: { ecmaVersion: 2021 },
100+
errors: [{
101+
messageId: "noFunctionConstructor",
102+
type: "CallExpression"
103+
}]
104+
},
58105
{
59106
code: "const fn = () => { class Function {} }; new Function('', '')",
60107
parserOptions: {

0 commit comments

Comments
 (0)