1/** 2 * @fileoverview A rule to suggest using of the spread operator instead of `.apply()`. 3 * @author Toru Nagashima 4 */ 5 6"use strict"; 7 8const astUtils = require("./utils/ast-utils"); 9 10//------------------------------------------------------------------------------ 11// Helpers 12//------------------------------------------------------------------------------ 13 14/** 15 * Checks whether or not a node is a `.apply()` for variadic. 16 * @param {ASTNode} node A CallExpression node to check. 17 * @returns {boolean} Whether or not the node is a `.apply()` for variadic. 18 */ 19function isVariadicApplyCalling(node) { 20 return ( 21 astUtils.isSpecificMemberAccess(node.callee, null, "apply") && 22 node.arguments.length === 2 && 23 node.arguments[1].type !== "ArrayExpression" && 24 node.arguments[1].type !== "SpreadElement" 25 ); 26} 27 28/** 29 * Checks whether or not `thisArg` is not changed by `.apply()`. 30 * @param {ASTNode|null} expectedThis The node that is the owner of the applied function. 31 * @param {ASTNode} thisArg The node that is given to the first argument of the `.apply()`. 32 * @param {RuleContext} context The ESLint rule context object. 33 * @returns {boolean} Whether or not `thisArg` is not changed by `.apply()`. 34 */ 35function isValidThisArg(expectedThis, thisArg, context) { 36 if (!expectedThis) { 37 return astUtils.isNullOrUndefined(thisArg); 38 } 39 return astUtils.equalTokens(expectedThis, thisArg, context); 40} 41 42//------------------------------------------------------------------------------ 43// Rule Definition 44//------------------------------------------------------------------------------ 45 46module.exports = { 47 meta: { 48 type: "suggestion", 49 50 docs: { 51 description: "require spread operators instead of `.apply()`", 52 category: "ECMAScript 6", 53 recommended: false, 54 url: "https://eslint.org/docs/rules/prefer-spread" 55 }, 56 57 schema: [], 58 fixable: null, 59 60 messages: { 61 preferSpread: "Use the spread operator instead of '.apply()'." 62 } 63 }, 64 65 create(context) { 66 const sourceCode = context.getSourceCode(); 67 68 return { 69 CallExpression(node) { 70 if (!isVariadicApplyCalling(node)) { 71 return; 72 } 73 74 const applied = astUtils.skipChainExpression(astUtils.skipChainExpression(node.callee).object); 75 const expectedThis = (applied.type === "MemberExpression") ? applied.object : null; 76 const thisArg = node.arguments[0]; 77 78 if (isValidThisArg(expectedThis, thisArg, sourceCode)) { 79 context.report({ 80 node, 81 messageId: "preferSpread" 82 }); 83 } 84 } 85 }; 86 } 87}; 88