1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | "use strict";
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | const astUtils = require("./utils/ast-utils");
|
13 | const { CALL, ReferenceTracker } = require("eslint-utils");
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | const PRECEDENCE_OF_EXPONENTIATION_EXPR = astUtils.getPrecedence({ type: "BinaryExpression", operator: "**" });
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 | function doesBaseNeedParens(base) {
|
27 | return (
|
28 |
|
29 |
|
30 | astUtils.getPrecedence(base) <= PRECEDENCE_OF_EXPONENTIATION_EXPR ||
|
31 |
|
32 |
|
33 | base.type === "AwaitExpression" ||
|
34 | base.type === "UnaryExpression"
|
35 | );
|
36 | }
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 | function doesExponentNeedParens(exponent) {
|
44 |
|
45 |
|
46 | return astUtils.getPrecedence(exponent) < PRECEDENCE_OF_EXPONENTIATION_EXPR;
|
47 | }
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 | function doesExponentiationExpressionNeedParens(node, sourceCode) {
|
56 | const parent = node.parent.type === "ChainExpression" ? node.parent.parent : node.parent;
|
57 |
|
58 | const needsParens = (
|
59 | parent.type === "ClassDeclaration" ||
|
60 | (
|
61 | parent.type.endsWith("Expression") &&
|
62 | astUtils.getPrecedence(parent) >= PRECEDENCE_OF_EXPONENTIATION_EXPR &&
|
63 | !(parent.type === "BinaryExpression" && parent.operator === "**" && parent.right === node) &&
|
64 | !((parent.type === "CallExpression" || parent.type === "NewExpression") && parent.arguments.includes(node)) &&
|
65 | !(parent.type === "MemberExpression" && parent.computed && parent.property === node) &&
|
66 | !(parent.type === "ArrayExpression")
|
67 | )
|
68 | );
|
69 |
|
70 | return needsParens && !astUtils.isParenthesised(sourceCode, node);
|
71 | }
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 | function parenthesizeIfShould(text, shouldParenthesize) {
|
80 | return shouldParenthesize ? `(${text})` : text;
|
81 | }
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 | module.exports = {
|
88 | meta: {
|
89 | type: "suggestion",
|
90 |
|
91 | docs: {
|
92 | description: "disallow the use of `Math.pow` in favor of the `**` operator",
|
93 | category: "Stylistic Issues",
|
94 | recommended: false,
|
95 | url: "https://eslint.org/docs/rules/prefer-exponentiation-operator"
|
96 | },
|
97 |
|
98 | schema: [],
|
99 | fixable: "code",
|
100 |
|
101 | messages: {
|
102 | useExponentiation: "Use the '**' operator instead of 'Math.pow'."
|
103 | }
|
104 | },
|
105 |
|
106 | create(context) {
|
107 | const sourceCode = context.getSourceCode();
|
108 |
|
109 | |
110 |
|
111 |
|
112 |
|
113 |
|
114 | function report(node) {
|
115 | context.report({
|
116 | node,
|
117 | messageId: "useExponentiation",
|
118 | fix(fixer) {
|
119 | if (
|
120 | node.arguments.length !== 2 ||
|
121 | node.arguments.some(arg => arg.type === "SpreadElement") ||
|
122 | sourceCode.getCommentsInside(node).length > 0
|
123 | ) {
|
124 | return null;
|
125 | }
|
126 |
|
127 | const base = node.arguments[0],
|
128 | exponent = node.arguments[1],
|
129 | baseText = sourceCode.getText(base),
|
130 | exponentText = sourceCode.getText(exponent),
|
131 | shouldParenthesizeBase = doesBaseNeedParens(base),
|
132 | shouldParenthesizeExponent = doesExponentNeedParens(exponent),
|
133 | shouldParenthesizeAll = doesExponentiationExpressionNeedParens(node, sourceCode);
|
134 |
|
135 | let prefix = "",
|
136 | suffix = "";
|
137 |
|
138 | if (!shouldParenthesizeAll) {
|
139 | if (!shouldParenthesizeBase) {
|
140 | const firstReplacementToken = sourceCode.getFirstToken(base),
|
141 | tokenBefore = sourceCode.getTokenBefore(node);
|
142 |
|
143 | if (
|
144 | tokenBefore &&
|
145 | tokenBefore.range[1] === node.range[0] &&
|
146 | !astUtils.canTokensBeAdjacent(tokenBefore, firstReplacementToken)
|
147 | ) {
|
148 | prefix = " ";
|
149 | }
|
150 | }
|
151 | if (!shouldParenthesizeExponent) {
|
152 | const lastReplacementToken = sourceCode.getLastToken(exponent),
|
153 | tokenAfter = sourceCode.getTokenAfter(node);
|
154 |
|
155 | if (
|
156 | tokenAfter &&
|
157 | node.range[1] === tokenAfter.range[0] &&
|
158 | !astUtils.canTokensBeAdjacent(lastReplacementToken, tokenAfter)
|
159 | ) {
|
160 | suffix = " ";
|
161 | }
|
162 | }
|
163 | }
|
164 |
|
165 | const baseReplacement = parenthesizeIfShould(baseText, shouldParenthesizeBase),
|
166 | exponentReplacement = parenthesizeIfShould(exponentText, shouldParenthesizeExponent),
|
167 | replacement = parenthesizeIfShould(`${baseReplacement}**${exponentReplacement}`, shouldParenthesizeAll);
|
168 |
|
169 | return fixer.replaceText(node, `${prefix}${replacement}${suffix}`);
|
170 | }
|
171 | });
|
172 | }
|
173 |
|
174 | return {
|
175 | Program() {
|
176 | const scope = context.getScope();
|
177 | const tracker = new ReferenceTracker(scope);
|
178 | const trackMap = {
|
179 | Math: {
|
180 | pow: { [CALL]: true }
|
181 | }
|
182 | };
|
183 |
|
184 | for (const { node } of tracker.iterateGlobalReferences(trackMap)) {
|
185 | report(node);
|
186 | }
|
187 | }
|
188 | };
|
189 | }
|
190 | };
|