UNPKG

@locker/eslint-plugin-unsafe-types

Version:
145 lines (125 loc) 5.41 kB
/** * @fileoverview Rule to flag use of implied eval via setTimeout and setInterval. * Code specifically written by LWS team is marked between // LWS BEGIN and // LWS END comments. * Original source https://github.com/eslint/eslint/blob/main/lib/rules/no-implied-eval.js * @author Lightning Web Security Team */ 'use strict'; //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ const { getStaticValue } = require('@eslint-community/eslint-utils'); const astUtils = require('../utils/ast-utils'); //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ /** @type {import('../shared/types').Rule} */ module.exports = { meta: { type: 'suggestion', docs: { description: 'Disallow the use of `eval()`-like methods', recommended: false, url: 'https://eslint.org/docs/latest/rules/no-implied-eval', }, schema: [], messages: { impliedEval: 'Implied eval. Consider passing a function instead of a string.', }, }, create(context) { const GLOBAL_CANDIDATES = Object.freeze(['global', 'window', 'globalThis']); const EVAL_LIKE_FUNC_PATTERN = /^(?:set(?:Interval|Timeout)|execScript)$/u; const sourceCode = context.sourceCode || context.getSourceCode(); const getScope = function (node) { if (typeof sourceCode.getScope === 'function') { return sourceCode.getScope(node); } if (typeof context.getScope === 'function') { return context.getScope(); } throw new Error('Cannot get scope of current node!'); }; /** * Checks whether a node is evaluated as a string or not. * @param {ASTNode} node A node to check. * @returns {boolean} True if the node is evaluated as a string. */ function isEvaluatedString(node) { if ( (node.type === 'Literal' && typeof node.value === 'string') || node.type === 'TemplateLiteral' ) { return true; } if (node.type === 'BinaryExpression' && node.operator === '+') { return isEvaluatedString(node.left) || isEvaluatedString(node.right); } return false; } /** * Reports if the `CallExpression` node has evaluated argument. * @param {ASTNode} node A CallExpression to check. * @returns {void} */ function reportImpliedEvalCallExpression(node) { const [firstArgument] = node.arguments; if (firstArgument) { // LWS BEGIN if (astUtils.isSignatureCall(firstArgument)) { return; } // LWS END const staticValue = getStaticValue(firstArgument, getScope(node)); const isStaticString = staticValue && typeof staticValue.value === 'string'; const isString = isStaticString || isEvaluatedString(firstArgument); if (isString) { context.report({ node, messageId: 'impliedEval', }); } } } /** * Reports calls of `implied eval` via the global references. * @param {Variable} globalVar A global variable to check. * @returns {void} */ function reportImpliedEvalViaGlobal(globalVar) { const { references, name } = globalVar; references.forEach((ref) => { const identifier = ref.identifier; let node = identifier.parent; while (astUtils.isSpecificMemberAccess(node, null, name)) { node = node.parent; } if (astUtils.isSpecificMemberAccess(node, null, EVAL_LIKE_FUNC_PATTERN)) { const calleeNode = node.parent.type === 'ChainExpression' ? node.parent : node; const parent = calleeNode.parent; if (parent.type === 'CallExpression' && parent.callee === calleeNode) { reportImpliedEvalCallExpression(parent); } } }); } //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- return { CallExpression(node) { if (astUtils.isSpecificId(node.callee, EVAL_LIKE_FUNC_PATTERN)) { reportImpliedEvalCallExpression(node); } }, 'Program:exit'(node) { const globalScope = getScope(node); GLOBAL_CANDIDATES.map((candidate) => astUtils.getVariableByName(globalScope, candidate) ) .filter((globalVar) => !!globalVar && globalVar.defs.length === 0) .forEach(reportImpliedEvalViaGlobal); }, }; }, };