• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Rule to flag use of implied eval via setTimeout and setInterval
3 * @author James Allardice
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Requirements
10//------------------------------------------------------------------------------
11
12const astUtils = require("./utils/ast-utils");
13const { getStaticValue } = require("eslint-utils");
14
15//------------------------------------------------------------------------------
16// Rule Definition
17//------------------------------------------------------------------------------
18
19module.exports = {
20    meta: {
21        type: "suggestion",
22
23        docs: {
24            description: "disallow the use of `eval()`-like methods",
25            category: "Best Practices",
26            recommended: false,
27            url: "https://eslint.org/docs/rules/no-implied-eval"
28        },
29
30        schema: [],
31
32        messages: {
33            impliedEval: "Implied eval. Consider passing a function instead of a string."
34        }
35    },
36
37    create(context) {
38        const GLOBAL_CANDIDATES = Object.freeze(["global", "window", "globalThis"]);
39        const EVAL_LIKE_FUNC_PATTERN = /^(?:set(?:Interval|Timeout)|execScript)$/u;
40
41        /**
42         * Checks whether a node is evaluated as a string or not.
43         * @param {ASTNode} node A node to check.
44         * @returns {boolean} True if the node is evaluated as a string.
45         */
46        function isEvaluatedString(node) {
47            if (
48                (node.type === "Literal" && typeof node.value === "string") ||
49                node.type === "TemplateLiteral"
50            ) {
51                return true;
52            }
53            if (node.type === "BinaryExpression" && node.operator === "+") {
54                return isEvaluatedString(node.left) || isEvaluatedString(node.right);
55            }
56            return false;
57        }
58
59        /**
60         * Reports if the `CallExpression` node has evaluated argument.
61         * @param {ASTNode} node A CallExpression to check.
62         * @returns {void}
63         */
64        function reportImpliedEvalCallExpression(node) {
65            const [firstArgument] = node.arguments;
66
67            if (firstArgument) {
68
69                const staticValue = getStaticValue(firstArgument, context.getScope());
70                const isStaticString = staticValue && typeof staticValue.value === "string";
71                const isString = isStaticString || isEvaluatedString(firstArgument);
72
73                if (isString) {
74                    context.report({
75                        node,
76                        messageId: "impliedEval"
77                    });
78                }
79            }
80
81        }
82
83        /**
84         * Reports calls of `implied eval` via the global references.
85         * @param {Variable} globalVar A global variable to check.
86         * @returns {void}
87         */
88        function reportImpliedEvalViaGlobal(globalVar) {
89            const { references, name } = globalVar;
90
91            references.forEach(ref => {
92                const identifier = ref.identifier;
93                let node = identifier.parent;
94
95                while (astUtils.isSpecificMemberAccess(node, null, name)) {
96                    node = node.parent;
97                }
98
99                if (astUtils.isSpecificMemberAccess(node, null, EVAL_LIKE_FUNC_PATTERN)) {
100                    const calleeNode = node.parent.type === "ChainExpression" ? node.parent : node;
101                    const parent = calleeNode.parent;
102
103                    if (parent.type === "CallExpression" && parent.callee === calleeNode) {
104                        reportImpliedEvalCallExpression(parent);
105                    }
106                }
107            });
108        }
109
110        //--------------------------------------------------------------------------
111        // Public
112        //--------------------------------------------------------------------------
113
114        return {
115            CallExpression(node) {
116                if (astUtils.isSpecificId(node.callee, EVAL_LIKE_FUNC_PATTERN)) {
117                    reportImpliedEvalCallExpression(node);
118                }
119            },
120            "Program:exit"() {
121                const globalScope = context.getScope();
122
123                GLOBAL_CANDIDATES
124                    .map(candidate => astUtils.getVariableByName(globalScope, candidate))
125                    .filter(globalVar => !!globalVar && globalVar.defs.length === 0)
126                    .forEach(reportImpliedEvalViaGlobal);
127            }
128        };
129
130    }
131};
132