| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 | /** * @fileoverview Rule to disallow Math.pow in favor of the ** operator * @author Milos Djermanovic */"use strict";//------------------------------------------------------------------------------// Requirements//------------------------------------------------------------------------------const astUtils = require("./utils/ast-utils");const { CALL, ReferenceTracker } = require("eslint-utils");//------------------------------------------------------------------------------// Helpers//------------------------------------------------------------------------------const PRECEDENCE_OF_EXPONENTIATION_EXPR = astUtils.getPrecedence({ type: "BinaryExpression", operator: "**" });/** * Determines whether the given node needs parens if used as the base in an exponentiation binary expression. * @param {ASTNode} base The node to check. * @returns {boolean} `true` if the node needs to be parenthesised. */function doesBaseNeedParens(base) {    return (        // '**' is right-associative, parens are needed when Math.pow(a ** b, c) is converted to (a ** b) ** c        astUtils.getPrecedence(base) <= PRECEDENCE_OF_EXPONENTIATION_EXPR ||        // An unary operator cannot be used immediately before an exponentiation expression        base.type === "AwaitExpression" ||        base.type === "UnaryExpression"    );}/** * Determines whether the given node needs parens if used as the exponent in an exponentiation binary expression. * @param {ASTNode} exponent The node to check. * @returns {boolean} `true` if the node needs to be parenthesised. */function doesExponentNeedParens(exponent) {    // '**' is right-associative, there is no need for parens when Math.pow(a, b ** c) is converted to a ** b ** c    return astUtils.getPrecedence(exponent) < PRECEDENCE_OF_EXPONENTIATION_EXPR;}/** * Determines whether an exponentiation binary expression at the place of the given node would need parens. * @param {ASTNode} node A node that would be replaced by an exponentiation binary expression. * @param {SourceCode} sourceCode A SourceCode object. * @returns {boolean} `true` if the expression needs to be parenthesised. */function doesExponentiationExpressionNeedParens(node, sourceCode) {    const parent = node.parent.type === "ChainExpression" ? node.parent.parent : node.parent;    const needsParens = (        parent.type === "ClassDeclaration" ||        (            parent.type.endsWith("Expression") &&            astUtils.getPrecedence(parent) >= PRECEDENCE_OF_EXPONENTIATION_EXPR &&            !(parent.type === "BinaryExpression" && parent.operator === "**" && parent.right === node) &&            !((parent.type === "CallExpression" || parent.type === "NewExpression") && parent.arguments.includes(node)) &&            !(parent.type === "MemberExpression" && parent.computed && parent.property === node) &&            !(parent.type === "ArrayExpression")        )    );    return needsParens && !astUtils.isParenthesised(sourceCode, node);}/** * Optionally parenthesizes given text. * @param {string} text The text to parenthesize. * @param {boolean} shouldParenthesize If `true`, the text will be parenthesised. * @returns {string} parenthesised or unchanged text. */function parenthesizeIfShould(text, shouldParenthesize) {    return shouldParenthesize ? `(${text})` : text;}//------------------------------------------------------------------------------// Rule Definition//------------------------------------------------------------------------------/** @type {import('../shared/types').Rule} */module.exports = {    meta: {        type: "suggestion",        docs: {            description: "Disallow the use of `Math.pow` in favor of the `**` operator",            recommended: false,            url: "https://eslint.org/docs/rules/prefer-exponentiation-operator"        },        schema: [],        fixable: "code",        messages: {            useExponentiation: "Use the '**' operator instead of 'Math.pow'."        }    },    create(context) {        const sourceCode = context.getSourceCode();        /**         * Reports the given node.         * @param {ASTNode} node 'Math.pow()' node to report.         * @returns {void}         */        function report(node) {            context.report({                node,                messageId: "useExponentiation",                fix(fixer) {                    if (                        node.arguments.length !== 2 ||                        node.arguments.some(arg => arg.type === "SpreadElement") ||                        sourceCode.getCommentsInside(node).length > 0                    ) {                        return null;                    }                    const base = node.arguments[0],                        exponent = node.arguments[1],                        baseText = sourceCode.getText(base),                        exponentText = sourceCode.getText(exponent),                        shouldParenthesizeBase = doesBaseNeedParens(base),                        shouldParenthesizeExponent = doesExponentNeedParens(exponent),                        shouldParenthesizeAll = doesExponentiationExpressionNeedParens(node, sourceCode);                    let prefix = "",                        suffix = "";                    if (!shouldParenthesizeAll) {                        if (!shouldParenthesizeBase) {                            const firstReplacementToken = sourceCode.getFirstToken(base),                                tokenBefore = sourceCode.getTokenBefore(node);                            if (                                tokenBefore &&                                tokenBefore.range[1] === node.range[0] &&                                !astUtils.canTokensBeAdjacent(tokenBefore, firstReplacementToken)                            ) {                                prefix = " "; // a+Math.pow(++b, c) -> a+ ++b**c                            }                        }                        if (!shouldParenthesizeExponent) {                            const lastReplacementToken = sourceCode.getLastToken(exponent),                                tokenAfter = sourceCode.getTokenAfter(node);                            if (                                tokenAfter &&                                node.range[1] === tokenAfter.range[0] &&                                !astUtils.canTokensBeAdjacent(lastReplacementToken, tokenAfter)                            ) {                                suffix = " "; // Math.pow(a, b)in c -> a**b in c                            }                        }                    }                    const baseReplacement = parenthesizeIfShould(baseText, shouldParenthesizeBase),                        exponentReplacement = parenthesizeIfShould(exponentText, shouldParenthesizeExponent),                        replacement = parenthesizeIfShould(`${baseReplacement}**${exponentReplacement}`, shouldParenthesizeAll);                    return fixer.replaceText(node, `${prefix}${replacement}${suffix}`);                }            });        }        return {            Program() {                const scope = context.getScope();                const tracker = new ReferenceTracker(scope);                const trackMap = {                    Math: {                        pow: { [CALL]: true }                    }                };                for (const { node } of tracker.iterateGlobalReferences(trackMap)) {                    report(node);                }            }        };    }};
 |