diff --git a/docs/rules/no-new-func.md b/docs/rules/no-new-func.md index 16ac00c6acb..7bdfc9ec732 100644 --- a/docs/rules/no-new-func.md +++ b/docs/rules/no-new-func.md @@ -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: diff --git a/lib/rules/no-new-func.js b/lib/rules/no-new-func.js index eebc68c80c2..ddf61024dac 100644 --- a/lib/rules/no-new-func.js +++ b/lib/rules/no-new-func.js @@ -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" }); } diff --git a/tests/lib/rules/no-new-func.js b/tests/lib/rules/no-new-func.js index aa0542090e2..1ac02d97465 100644 --- a/tests/lib/rules/no-new-func.js +++ b/tests/lib/rules/no-new-func.js @@ -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: {