]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
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 | ||
12 | const astUtils = require("./utils/ast-utils"); | |
13 | const { getStaticValue } = require("eslint-utils"); | |
14 | ||
15 | //------------------------------------------------------------------------------ | |
16 | // Rule Definition | |
17 | //------------------------------------------------------------------------------ | |
18 | ||
34eeec05 | 19 | /** @type {import('../shared/types').Rule} */ |
eb39fafa DC |
20 | module.exports = { |
21 | meta: { | |
22 | type: "suggestion", | |
23 | ||
24 | docs: { | |
8f9d1d4d | 25 | description: "Disallow the use of `eval()`-like methods", |
eb39fafa DC |
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) { | |
eb39fafa | 38 | const GLOBAL_CANDIDATES = Object.freeze(["global", "window", "globalThis"]); |
6f036462 | 39 | const EVAL_LIKE_FUNC_PATTERN = /^(?:set(?:Interval|Timeout)|execScript)$/u; |
eb39fafa DC |
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 | ||
eb39fafa DC |
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 | ||
6f036462 | 95 | while (astUtils.isSpecificMemberAccess(node, null, name)) { |
eb39fafa DC |
96 | node = node.parent; |
97 | } | |
98 | ||
6f036462 TL |
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; | |
eb39fafa | 102 | |
6f036462 | 103 | if (parent.type === "CallExpression" && parent.callee === calleeNode) { |
eb39fafa DC |
104 | reportImpliedEvalCallExpression(parent); |
105 | } | |
106 | } | |
107 | }); | |
108 | } | |
109 | ||
110 | //-------------------------------------------------------------------------- | |
111 | // Public | |
112 | //-------------------------------------------------------------------------- | |
113 | ||
114 | return { | |
115 | CallExpression(node) { | |
6f036462 | 116 | if (astUtils.isSpecificId(node.callee, EVAL_LIKE_FUNC_PATTERN)) { |
eb39fafa DC |
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 | }; |