Skip to content
Permalink
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 <milos.djermanovic@gmail.com>

* Fix: revise no-new-func logic

Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>

* 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 <milos.djermanovic@gmail.com>

* Docs: add no-new-func example

Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>

* Fix: use maybeCallee instead of parent in no-new-func

Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>

* add tests

* check method name

Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>
  • Loading branch information
archmoj and mdjermanovic committed Sep 23, 2021
1 parent 7f2346b commit 14a4739ab2233acef995a6dde233de05d067a0f3
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 9 deletions.
@@ -1,12 +1,16 @@
# Disallow Function Constructor (no-new-func)

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

```js
var x = new Function("a", "b", "return a + b");
var x = Function("a", "b", "return a + b");
var x = Function.call(null, "a", "b", "return a + b");
var x = Function.apply(null, ["a", "b", "return a + b"]);
var x = Function.bind(null, "a", "b", "return a + b")();
```

This is considered by many to be a bad practice due to the difficulty in debugging and reading these types of functions.
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.

## Rule Details

@@ -19,6 +23,10 @@ Examples of **incorrect** code for this rule:
var x = new Function("a", "b", "return a + b");
var x = Function("a", "b", "return a + b");
var x = Function.call(null, "a", "b", "return a + b");
var x = Function.apply(null, ["a", "b", "return a + b"]);
var x = Function.bind(null, "a", "b", "return a + b")();
var f = Function.bind(null, "a", "b", "return a + b"); // assuming that the result of Function.bind(...) will be eventually called.
```

Examples of **correct** code for this rule:
@@ -5,6 +5,18 @@

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

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

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

const callMethods = new Set(["apply", "bind", "call"]);

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -37,14 +49,30 @@ module.exports = {
variable.references.forEach(ref => {
const node = ref.identifier;
const { parent } = node;
let evalNode;

if (parent) {
if (node === parent.callee && (
parent.type === "NewExpression" ||
parent.type === "CallExpression"
)) {
evalNode = parent;
} else if (
parent.type === "MemberExpression" &&
node === parent.object &&
callMethods.has(astUtils.getStaticPropertyName(parent))
) {
const maybeCallee = parent.parent.type === "ChainExpression" ? parent.parent : parent;

if (maybeCallee.parent.type === "CallExpression" && maybeCallee.parent.callee === maybeCallee) {
evalNode = maybeCallee.parent;
}
}
}

if (
parent &&
(parent.type === "NewExpression" || parent.type === "CallExpression") &&
node === parent.callee
) {
if (evalNode) {
context.report({
node: parent,
node: evalNode,
messageId: "noFunctionConstructor"
});
}
@@ -38,7 +38,11 @@ ruleTester.run("no-new-func", rule, {
"var fn = function () { function Function() {}; Function() }",
"var x = function Function() { Function(); }",
"call(Function)",
"new Class(Function)"
"new Class(Function)",
"foo[Function]()",
"foo(Function.bind)",
"Function.toString()",
"Function[call]()"
],
invalid: [
{
@@ -55,6 +59,49 @@ ruleTester.run("no-new-func", rule, {
type: "CallExpression"
}]
},
{
code: "var a = Function.call(null, \"b\", \"c\", \"return b+c\");",
errors: [{
messageId: "noFunctionConstructor",
type: "CallExpression"
}]
},
{
code: "var a = Function.apply(null, [\"b\", \"c\", \"return b+c\"]);",
errors: [{
messageId: "noFunctionConstructor",
type: "CallExpression"
}]
},
{
code: "var a = Function.bind(null, \"b\", \"c\", \"return b+c\")();",
errors: [{
messageId: "noFunctionConstructor",
type: "CallExpression"
}]
},
{
code: "var a = Function.bind(null, \"b\", \"c\", \"return b+c\");",
errors: [{
messageId: "noFunctionConstructor",
type: "CallExpression"
}]
},
{
code: "var a = Function[\"call\"](null, \"b\", \"c\", \"return b+c\");",
errors: [{
messageId: "noFunctionConstructor",
type: "CallExpression"
}]
},
{
code: "var a = (Function?.call)(null, \"b\", \"c\", \"return b+c\");",
parserOptions: { ecmaVersion: 2021 },
errors: [{
messageId: "noFunctionConstructor",
type: "CallExpression"
}]
},
{
code: "const fn = () => { class Function {} }; new Function('', '')",
parserOptions: {

0 comments on commit 14a4739

Please sign in to comment.