no-eval.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. /**
  2. * @fileoverview Rule to flag use of eval() statement
  3. * @author Nicholas C. Zakas
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("./utils/ast-utils");
  10. //------------------------------------------------------------------------------
  11. // Helpers
  12. //------------------------------------------------------------------------------
  13. const candidatesOfGlobalObject = Object.freeze([
  14. "global",
  15. "window"
  16. ]);
  17. /**
  18. * Checks a given node is a Identifier node of the specified name.
  19. * @param {ASTNode} node A node to check.
  20. * @param {string} name A name to check.
  21. * @returns {boolean} `true` if the node is a Identifier node of the name.
  22. */
  23. function isIdentifier(node, name) {
  24. return node.type === "Identifier" && node.name === name;
  25. }
  26. /**
  27. * Checks a given node is a Literal node of the specified string value.
  28. * @param {ASTNode} node A node to check.
  29. * @param {string} name A name to check.
  30. * @returns {boolean} `true` if the node is a Literal node of the name.
  31. */
  32. function isConstant(node, name) {
  33. switch (node.type) {
  34. case "Literal":
  35. return node.value === name;
  36. case "TemplateLiteral":
  37. return (
  38. node.expressions.length === 0 &&
  39. node.quasis[0].value.cooked === name
  40. );
  41. default:
  42. return false;
  43. }
  44. }
  45. /**
  46. * Checks a given node is a MemberExpression node which has the specified name's
  47. * property.
  48. * @param {ASTNode} node A node to check.
  49. * @param {string} name A name to check.
  50. * @returns {boolean} `true` if the node is a MemberExpression node which has
  51. * the specified name's property
  52. */
  53. function isMember(node, name) {
  54. return (
  55. node.type === "MemberExpression" &&
  56. (node.computed ? isConstant : isIdentifier)(node.property, name)
  57. );
  58. }
  59. //------------------------------------------------------------------------------
  60. // Rule Definition
  61. //------------------------------------------------------------------------------
  62. module.exports = {
  63. meta: {
  64. type: "suggestion",
  65. docs: {
  66. description: "disallow the use of `eval()`",
  67. category: "Best Practices",
  68. recommended: false,
  69. url: "https://eslint.org/docs/rules/no-eval"
  70. },
  71. schema: [
  72. {
  73. type: "object",
  74. properties: {
  75. allowIndirect: { type: "boolean", default: false }
  76. },
  77. additionalProperties: false
  78. }
  79. ],
  80. messages: {
  81. unexpected: "eval can be harmful."
  82. }
  83. },
  84. create(context) {
  85. const allowIndirect = Boolean(
  86. context.options[0] &&
  87. context.options[0].allowIndirect
  88. );
  89. const sourceCode = context.getSourceCode();
  90. let funcInfo = null;
  91. /**
  92. * Pushs a variable scope (Program or Function) information to the stack.
  93. *
  94. * This is used in order to check whether or not `this` binding is a
  95. * reference to the global object.
  96. * @param {ASTNode} node A node of the scope. This is one of Program,
  97. * FunctionDeclaration, FunctionExpression, and ArrowFunctionExpression.
  98. * @returns {void}
  99. */
  100. function enterVarScope(node) {
  101. const strict = context.getScope().isStrict;
  102. funcInfo = {
  103. upper: funcInfo,
  104. node,
  105. strict,
  106. defaultThis: false,
  107. initialized: strict
  108. };
  109. }
  110. /**
  111. * Pops a variable scope from the stack.
  112. * @returns {void}
  113. */
  114. function exitVarScope() {
  115. funcInfo = funcInfo.upper;
  116. }
  117. /**
  118. * Reports a given node.
  119. *
  120. * `node` is `Identifier` or `MemberExpression`.
  121. * The parent of `node` might be `CallExpression`.
  122. *
  123. * The location of the report is always `eval` `Identifier` (or possibly
  124. * `Literal`). The type of the report is `CallExpression` if the parent is
  125. * `CallExpression`. Otherwise, it's the given node type.
  126. * @param {ASTNode} node A node to report.
  127. * @returns {void}
  128. */
  129. function report(node) {
  130. const parent = node.parent;
  131. const locationNode = node.type === "MemberExpression"
  132. ? node.property
  133. : node;
  134. const reportNode = parent.type === "CallExpression" && parent.callee === node
  135. ? parent
  136. : node;
  137. context.report({
  138. node: reportNode,
  139. loc: locationNode.loc.start,
  140. messageId: "unexpected"
  141. });
  142. }
  143. /**
  144. * Reports accesses of `eval` via the global object.
  145. * @param {eslint-scope.Scope} globalScope The global scope.
  146. * @returns {void}
  147. */
  148. function reportAccessingEvalViaGlobalObject(globalScope) {
  149. for (let i = 0; i < candidatesOfGlobalObject.length; ++i) {
  150. const name = candidatesOfGlobalObject[i];
  151. const variable = astUtils.getVariableByName(globalScope, name);
  152. if (!variable) {
  153. continue;
  154. }
  155. const references = variable.references;
  156. for (let j = 0; j < references.length; ++j) {
  157. const identifier = references[j].identifier;
  158. let node = identifier.parent;
  159. // To detect code like `window.window.eval`.
  160. while (isMember(node, name)) {
  161. node = node.parent;
  162. }
  163. // Reports.
  164. if (isMember(node, "eval")) {
  165. report(node);
  166. }
  167. }
  168. }
  169. }
  170. /**
  171. * Reports all accesses of `eval` (excludes direct calls to eval).
  172. * @param {eslint-scope.Scope} globalScope The global scope.
  173. * @returns {void}
  174. */
  175. function reportAccessingEval(globalScope) {
  176. const variable = astUtils.getVariableByName(globalScope, "eval");
  177. if (!variable) {
  178. return;
  179. }
  180. const references = variable.references;
  181. for (let i = 0; i < references.length; ++i) {
  182. const reference = references[i];
  183. const id = reference.identifier;
  184. if (id.name === "eval" && !astUtils.isCallee(id)) {
  185. // Is accessing to eval (excludes direct calls to eval)
  186. report(id);
  187. }
  188. }
  189. }
  190. if (allowIndirect) {
  191. // Checks only direct calls to eval. It's simple!
  192. return {
  193. "CallExpression:exit"(node) {
  194. const callee = node.callee;
  195. if (isIdentifier(callee, "eval")) {
  196. report(callee);
  197. }
  198. }
  199. };
  200. }
  201. return {
  202. "CallExpression:exit"(node) {
  203. const callee = node.callee;
  204. if (isIdentifier(callee, "eval")) {
  205. report(callee);
  206. }
  207. },
  208. Program(node) {
  209. const scope = context.getScope(),
  210. features = context.parserOptions.ecmaFeatures || {},
  211. strict =
  212. scope.isStrict ||
  213. node.sourceType === "module" ||
  214. (features.globalReturn && scope.childScopes[0].isStrict);
  215. funcInfo = {
  216. upper: null,
  217. node,
  218. strict,
  219. defaultThis: true,
  220. initialized: true
  221. };
  222. },
  223. "Program:exit"() {
  224. const globalScope = context.getScope();
  225. exitVarScope();
  226. reportAccessingEval(globalScope);
  227. reportAccessingEvalViaGlobalObject(globalScope);
  228. },
  229. FunctionDeclaration: enterVarScope,
  230. "FunctionDeclaration:exit": exitVarScope,
  231. FunctionExpression: enterVarScope,
  232. "FunctionExpression:exit": exitVarScope,
  233. ArrowFunctionExpression: enterVarScope,
  234. "ArrowFunctionExpression:exit": exitVarScope,
  235. ThisExpression(node) {
  236. if (!isMember(node.parent, "eval")) {
  237. return;
  238. }
  239. /*
  240. * `this.eval` is found.
  241. * Checks whether or not the value of `this` is the global object.
  242. */
  243. if (!funcInfo.initialized) {
  244. funcInfo.initialized = true;
  245. funcInfo.defaultThis = astUtils.isDefaultThisBinding(
  246. funcInfo.node,
  247. sourceCode
  248. );
  249. }
  250. if (!funcInfo.strict && funcInfo.defaultThis) {
  251. // `this.eval` is possible built-in `eval`.
  252. report(node.parent);
  253. }
  254. }
  255. };
  256. }
  257. };