1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | "use strict";
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | const astUtils = require("./utils/ast-utils");
|
13 | const { getStaticValue } = require("eslint-utils");
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | module.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 |
|
43 |
|
44 |
|
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 |
|
61 |
|
62 |
|
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 |
|
85 |
|
86 |
|
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 |
|
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 | };
|