diff --git a/src/configs/all.ts b/src/configs/all.ts index f61d9cb6d6a..02692f4d4b8 100644 --- a/src/configs/all.ts +++ b/src/configs/all.ts @@ -99,6 +99,7 @@ export const rules = { "ban-comma-operator": true, curly: true, forin: true, + "function-constructor": true, // "import-blacklist": no sensible default "label-position": true, "no-arg": true, diff --git a/src/configs/recommended.ts b/src/configs/recommended.ts index b9e81ec6fd4..0a06e82dd23 100644 --- a/src/configs/recommended.ts +++ b/src/configs/recommended.ts @@ -47,6 +47,7 @@ export const rules = { "cyclomatic-complexity": false, eofline: true, forin: true, + "function-constructor": true, "import-spacing": true, indent: { options: ["spaces"], diff --git a/src/rules/code-examples/functionConstructor.examples.ts b/src/rules/code-examples/functionConstructor.examples.ts new file mode 100644 index 00000000000..47b222b5595 --- /dev/null +++ b/src/rules/code-examples/functionConstructor.examples.ts @@ -0,0 +1,45 @@ +/** + * @license + * Copyright 2013 Palantir Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as Lint from "../../index"; + +export const codeExamples = [ + { + config: Lint.Utils.dedent` + "rules": { "function-constructor": true } + `, + description: "Use inline lambdas instead of calling Function", + fail: Lint.Utils.dedent` + let doesNothing = new Function(); + `, + pass: Lint.Utils.dedent` + let doesNothing = () => {}; + ` + }, + { + config: Lint.Utils.dedent` + "rules": { "function-constructor": true } + `, + description: "Use parameters instead of constructor strings", + fail: Lint.Utils.dedent` + let addNumbers = new Function("a", "b", "return a + b"); + `, + pass: Lint.Utils.dedent` + let addNumbers = (a, b) => a + b; + ` + } +]; diff --git a/src/rules/functionConstructorRule.ts b/src/rules/functionConstructorRule.ts new file mode 100644 index 00000000000..eacbb928dac --- /dev/null +++ b/src/rules/functionConstructorRule.ts @@ -0,0 +1,73 @@ +/** + * @license + * Copyright 2013 Palantir Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { isCallExpression, isIdentifier, isNewExpression } from "tsutils"; +import * as ts from "typescript"; + +import * as Lint from ".."; +import { codeExamples } from "./code-examples/functionConstructor.examples"; + +export class Rule extends Lint.Rules.AbstractRule { + public static metadata: Lint.IRuleMetadata = { + codeExamples, + description: Lint.Utils.dedent` + Prevents using the built-in Function constructor. + `, + optionExamples: [true], + options: null, + optionsDescription: "Not configurable.", + rationale: Lint.Utils.dedent` + Calling the constructor directly is similar to \`eval\`, which is a symptom of design issues. + String inputs don't receive type checking and can cause performance issues, particularly when dynamically created. + + If you need to dynamically create functions, use "factory" functions that themselves return functions. + `, + ruleName: "function-constructor", + type: "functionality", + typescriptOnly: false + }; + + public static FAILURE = "Do not use the Function constructor to create functions."; + + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithFunction(sourceFile, walk); + } +} + +function walk(context: Lint.WalkContext): void { + ts.forEachChild(context.sourceFile, function cb(node): void { + if (isFunctionCallOrNewExpression(node)) { + addFailureAtNode(node); + } + + ts.forEachChild(node, cb); + }); + + function addFailureAtNode(node: CallOrNewExpression) { + context.addFailureAtNode(node, Rule.FAILURE); + } +} + +function isFunctionCallOrNewExpression(node: ts.Node): node is CallOrNewExpression { + if (isCallExpression(node) || isNewExpression(node)) { + return isIdentifier(node.expression) && node.expression.text === "Function"; + } + + return false; +} + +type CallOrNewExpression = ts.CallExpression | ts.NewExpression; diff --git a/test/rules/function-constructor/test.ts.lint b/test/rules/function-constructor/test.ts.lint new file mode 100644 index 00000000000..cc2f3829427 --- /dev/null +++ b/test/rules/function-constructor/test.ts.lint @@ -0,0 +1,19 @@ +function one() { } +const two = function () { }; +const three = () => {}; + +const noParametersNew = Function(); + ~~~~~~~~~~ [0] +const oneParameterNew = Function("a"); + ~~~~~~~~~~~~~ [0] +const twoParametersNew = Function("a", "b"); + ~~~~~~~~~~~~~~~~~~ [0] + +const noParametersNew = new Function(); + ~~~~~~~~~~~~~~ [0] +const oneParameterNew = new Function("a"); + ~~~~~~~~~~~~~~~~~ [0] +const twoParametersNew = new Function("a", "b"); + ~~~~~~~~~~~~~~~~~~~~~~ [0] + +[0]: Do not use the Function constructor to create functions. diff --git a/test/rules/function-constructor/tslint.json b/test/rules/function-constructor/tslint.json new file mode 100644 index 00000000000..9f48491ff28 --- /dev/null +++ b/test/rules/function-constructor/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "function-constructor": true + } +}