eslint prefer-exponentiation-operator
labelled safe
Analysis Nemotron 3 Super
The rule flags any call expression where the callee is exactly 'Math.pow' and replaces it with the '**
Fix: Replace every occurrence of Math.pow(base, exponent) with base ** exponent.
10 hazards
- If either operand contains side effects (e.g., function calls), changing from Math.pow(a(), b()) to a() ** b() may alter evaluation order if the language specification differed—though both evaluate left-to-right, the rewrite assumes identical semantics and any future spec change could break equivalence.
- If 'Math' is rebound in the current scope (e.g., var Math = {};), the original code would reference the binding while the fix assumes the global Math, potentially causing a ReferenceError or wrong value.
- If 'pow' is rebound as a property of Math (e.g., Math.pow = function(){return 0;}), the fix bypasses the overridden method and uses the built-in operator, altering behavior.
- If the code relies on Math.pow's specific handling of NaN or signed zeros (though ** matches), any future divergence in engine implementations could cause subtle differences.
- If the expression includes decorators, annotations, or non-standard syntax that attaches meaning to the call site, stripping the call form could lose metadata.
- If the codebase uses a transpiler that treats Math.pow specially (e.g., for polyfilling older engines), replacing it with ** might interfere with the transpiler's output.
- If a comment is attached to the Math token or the dot (e.g., Math/*ignore*/.pow(2,3)), the autofix may discard the comment when rewriting to 2 ** 3.
- If the arguments are expressions that themselves contain commas (e.g., Math.pow(a, b, c)), the rule does not fire because the shape is invalid, but if mistakenly applied, the fix would produce a SyntaxError.
- If the code is parsed in strict mode and Math is not defined, the original throws a ReferenceError; the fix would throw a ReferenceError earlier when evaluating the left-hand side if it is an undefined variable, changing timing but not outcome.
- If the exponentiation operator is used in a context where an expression is expected but the surrounding code relies on the call being a valid LeftHandSideExpression (e.g., in destructuring like [Math.pow] = []), the fix breaks because ** cannot appear there.
Findings
| program | oracle | label | judge | status | summary |
|---|---|---|---|---|---|
| r2-p003 | fix-changes-behavior | labelled safe | real · high | related | The program redefines `Math.pow` to perform addition, so `Math.pow(2, 3)` returns 5. The fix replaces the call with `2 ** 3`, which uses the built-in exponenti… |
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fired, no fix | Comment Discard Hazard | If a comment is attached to the Math token or the dot (e.g., Math/*ignore*/.pow(2,3)), the autofix may discard the comment when rewriting to 2 ** 3. |
| r1-p002 | fix ok | NaN/Signed Zero Handling Hazard | If the code relies on Math.pow's specific handling of NaN or signed zeros (though ** matches), any future divergence in engine implementations could cause subtle differences. |
| r1-p003 | silent | Comma Expression Argument Hazard | If the arguments are expressions that themselves contain commas (e.g., Math.pow(a, b, c)), the rule does not fire because the shape is invalid, but if mistakenly applied, the fix would produce a Synta |
| r1-p004 | fix ok | Async/Await/Yield Position Hazard | async/await ordering, generators and yield position |
| r1-p005 | silent prediction off | Math Rebound in Scope Hazard | If 'Math' is rebound in the current scope (e.g., var Math = {};), the original code would reference the binding while the fix assumes the global Math, potentially causing a ReferenceError or wrong val |
| r1-p006 | fix ok | __proto__/Prototype Access Hazard | __proto__ keys, prototype access, Object.assign vs spread |
| r1-p007 | fix ok | Numeric/String Coercion Hazard | numeric/string coercion: + vs template literal, objects with toString/valueOf, Symbol, BigInt |
| r1-p008 | fix ok | Null/Undefined Distinction Hazard | null/undefined distinction, optional chaining short-circuit, nullish vs falsy |
| r2-p001 | fix ok | Side Effects in Operands | If either operand contains side effects (e.g., function calls), changing from Math.pow(a(), b()) to a() ** b() may alter evaluation order if the language specification differed—though both evaluate le |
| r2-p002 | fix ok | Null/Undefined Distinction | null/undefined distinction, optional chaining short-circuit, nullish vs falsy |
| r2-p003 | fix-changes-behavior | Rebound Math.pow Property | If 'pow' is rebound as a property of Math (e.g., Math.pow = function(){return 0;}), the fix bypasses the overridden method and uses the built-in operator, altering behavior. |
| r2-p004 | fix ok | Sparse Arrays and Holes | sparse arrays, holes, Array(n) vs [n], array-likes |
| r2-p005 | fix ok | Arguments Object and Parameters | arguments object, function.length, default parameters, rest parameters |
| r2-p006 | invalid | Strict vs Sloppy Mode Semantics | strict vs sloppy mode semantics |
| r2-p007 | silent | Comma Expressions in Arguments | If the arguments are expressions that themselves contain commas (e.g., Math.pow(a, b, c)), the rule does not fire because the shape is invalid, but if mistakenly applied, the fix would produce a Synta |
| r2-p008 | fix ok | Temporal Dead Zone After Hoisting | temporal dead zone after code is hoisted or reordered |
Sources
r1-p001 · Comment Discard Hazard
1let result;
2try {
3 // Comment attached after dot
4 result = Math//comment
5 .pow(2, 3);
6 console.log('Success:', result);
7} catch (e) {
8 console.log('Error:', e.name, e.message);
9}
Success: 8
r1-p002 · NaN/Signed Zero Handling Hazard
1console.log(Math.pow(-0, -1)); // Should be -Infinity
2console.log((-0) ** (-1)); // Same currently, but testing equivalence
3console.log(Math.pow(NaN, 0)); // Should be 1
4console.log(NaN ** 0); // Same currently
--- orig.js 2026-10-22 14:26:47.923829742 -0400 +++ fixed.js 2026-10-22 14:26:50.545779858 -0400 @@ -1,4 +1,4 @@ -console.log(Math.pow(-0, -1)); // Should be -Infinity +console.log((-0)**-1); // Should be -Infinity console.log((-0) ** (-1)); // Same currently, but testing equivalence -console.log(Math.pow(NaN, 0)); // Should be 1 +console.log(NaN**0); // Should be 1 console.log(NaN ** 0); // Same currently
-Infinity -Infinity 1 1
r1-p003 · Comma Expression Argument Hazard
1const a = 2, b = 3, c = 4;
2// This has three args -> rule should NOT fire
3if (false) {
4 // Hypothetical broken fix: Math.pow(a, b, c) -> a ** b, c
5 // Would be syntax error if uncommented
6 // Math.pow(a, b, c);
7}
8console.log('a:', a, 'b:', b, 'c:', c);
a: 2 b: 3 c: 4
r1-p004 · Async/Await/Yield Position Hazard
1async function foo() {
2 const x = await Math//no comment
3 .pow(2, 3);
4 return x;
5}
6foo().then(r => console.log('Async result:', r));
7
8function* gen() {
9 yield Math.pow(2, 3);
10}
11const g = gen();
12console.log('Generator first yield:', g.next().value);
--- orig.js 2026-10-22 14:26:47.924829722 -0400 +++ fixed.js 2026-10-22 14:26:51.775756714 -0400 @@ -6,7 +6,7 @@ foo().then(r => console.log('Async result:', r)); function* gen() { - yield Math.pow(2, 3); + yield 2**3; } const g = gen(); console.log('Generator first yield:', g.next().value);
Generator first yield: 8 Async result: 8
r1-p005 · Math Rebound in Scope Hazard
1var Math = { pow: (b, e) => b * e }; // Local rebinding
2console.log('Local Math.pow(2,3):', Math.pow(2, 3));
3try {
4 // This would be rewritten to 2 ** 3 by the rule, ignoring local Math
5 // But we simulate what the broken fix would do:
6 const brokenFix = 2 ** 3;
7 console.log('Broken fix (2**3):', brokenFix);
8} catch (e) {
9 console.log('Error in fix simulation:', e.name, e.message);
10}
Local Math.pow(2,3): 6 Broken fix (2**3): 8
r1-p006 · __proto__/Prototype Access Hazard
1const obj = {};
2obj.__proto__.Math = { pow: (b, e) => b + e };
3console.log('obj.Math.pow(2,3):', obj.Math.pow(2, 3));
4// Rule would see obj.Math.pow and NOT fire (not direct Math)
5// So no rewrite happens here.
6console.log('Global Math.pow(2,3):', Math.pow(2, 3));
--- orig.js 2026-10-22 14:26:47.925829703 -0400 +++ fixed.js 2026-10-22 14:26:51.413763510 -0400 @@ -3,4 +3,4 @@ console.log('obj.Math.pow(2,3):', obj.Math.pow(2, 3)); // Rule would see obj.Math.pow and NOT fire (not direct Math) // So no rewrite happens here. -console.log('Global Math.pow(2,3):', Math.pow(2, 3)); +console.log('Global Math.pow(2,3):', 2**3);
obj.Math.pow(2,3): 5 Global Math.pow(2,3): 8
r1-p007 · Numeric/String Coercion Hazard
1const val = { valueOf: () => 2, toString: () => '2' };
2console.log('Math.pow(val, 3):', Math.pow(val, 3));
3console.log('val ** 3:', val ** 3);
4
5const sym = Symbol(1);
6try {
7 console.log(Math.pow(sym, 2));
8} catch (e) {
9 console.log('Math.pow(Symbol) error:', e.name);
10}
11try {
12 console.log(sym ** 2);
13} catch (e) {
14 console.log('Symbol ** error:', e.name);
15}
--- orig.js 2026-10-22 14:26:49.998790189 -0400 +++ fixed.js 2026-10-22 14:26:53.768719579 -0400 @@ -1,10 +1,10 @@ const val = { valueOf: () => 2, toString: () => '2' }; -console.log('Math.pow(val, 3):', Math.pow(val, 3)); +console.log('Math.pow(val, 3):', val**3); console.log('val ** 3:', val ** 3); const sym = Symbol(1); try { - console.log(Math.pow(sym, 2)); + console.log(sym**2); } catch (e) { console.log('Math.pow(Symbol) error:', e.name); }
Math.pow(val, 3): 8 val ** 3: 8 Math.pow(Symbol) error: TypeError Symbol ** error: TypeError
r1-p008 · Null/Undefined Distinction Hazard
1let x = null;
2console.log('x?.prop?.Math?.pow?.(2,3):', x?.prop?.Math?.pow?.(2, 3));
3
4x = { prop: { Math: { pow: (b, e) => b ** e } } };
5console.log('x?.prop?.Math?.pow?.(2,3):', x?.prop?.Math?.pow?.(2, 3));
6
7// Direct call that rule would fire on
8const base = 2;
9exp = 3;
10console.log('Math.pow(base,exp):', Math.pow(base, exp));
11console.log('base ** exp:', base ** exp);
--- orig.js 2026-10-22 14:26:50.434781954 -0400 +++ fixed.js 2026-10-22 14:26:53.066732602 -0400 @@ -7,5 +7,5 @@ // Direct call that rule would fire on const base = 2; exp = 3; -console.log('Math.pow(base,exp):', Math.pow(base, exp)); +console.log('Math.pow(base,exp):', base**exp); console.log('base ** exp:', base ** exp);
x?.prop?.Math?.pow?.(2,3): undefined x?.prop?.Math?.pow?.(2,3): 8 Math.pow(base,exp): 8 base ** exp: 8
r2-p001 · Side Effects in Operands
1let x = 0;
2const inc = () => {
3 x++;
4 return x;
5};
6console.log(Math.pow(inc(), inc()));
7x = 0;
8console.log(inc() ** inc());
--- orig.js 2026-10-22 14:27:00.173603221 -0400 +++ fixed.js 2026-10-22 14:27:02.863555701 -0400 @@ -3,6 +3,6 @@ x++; return x; }; -console.log(Math.pow(inc(), inc())); +console.log(inc()**inc()); x = 0; console.log(inc() ** inc());
1 1
r2-p002 · Null/Undefined Distinction
1let val = null;
2try {
3 console.log(Math.pow(val, 2));
4} catch (e) {
5 console.log(e.name + ': ' + e.message);
6}
7val = undefined;
8try {
9 console.log(val ** 2);
10} catch (e) {
11 console.log(e.name + ': ' + e.message);
12}
--- orig.js 2026-10-22 14:27:00.174603203 -0400 +++ fixed.js 2026-10-22 14:27:02.980553647 -0400 @@ -1,6 +1,6 @@ let val = null; try { - console.log(Math.pow(val, 2)); + console.log(val**2); } catch (e) { console.log(e.name + ': ' + e.message); }
0 NaN
r2-p003 · Rebound Math.pow Property
1Math.pow = function(base, exp) { return base + exp; };
2console.log(Math.pow(2, 3));
3depth = 0;
4while (depth < 1) {
5 console.log(2 ** 3);
6 depth++;
7}
--- orig.js 2026-10-22 14:27:00.174603203 -0400 +++ fixed.js 2026-10-22 14:27:02.947554227 -0400 @@ -1,5 +1,5 @@ Math.pow = function(base, exp) { return base + exp; }; -console.log(Math.pow(2, 3)); +console.log(2**3); depth = 0; while (depth < 1) { console.log(2 ** 3);
5 8
r2-p004 · Sparse Arrays and Holes
1const arr = Array(3);
2arr[2] = 2;
3console.log(Math.pow(arr[0], arr[2]));
4console.log(arr[0] ** arr[2]);
--- orig.js 2026-10-22 14:27:00.175603185 -0400 +++ fixed.js 2026-10-22 14:27:02.896555122 -0400 @@ -1,4 +1,4 @@ const arr = Array(3); arr[2] = 2; -console.log(Math.pow(arr[0], arr[2])); +console.log(arr[0]**arr[2]); console.log(arr[0] ** arr[2]);
NaN NaN
r2-p005 · Arguments Object and Parameters
1function test(a = 2, b = 3) {
2 console.log(arguments.length);
3 console.log(Math.pow(a, b));
4}
5test();
6function test2() {
7 console.log(arguments.length);
8 console.log((arguments[0] || 2) ** (arguments[1] || 3));
9}
10test2();
--- orig.js 2026-10-22 14:27:00.176603168 -0400 +++ fixed.js 2026-10-22 14:27:02.871555561 -0400 @@ -1,6 +1,6 @@ function test(a = 2, b = 3) { console.log(arguments.length); - console.log(Math.pow(a, b)); + console.log(a**b); } test(); function test2() {
0 8 0 8
r2-p006 · Strict vs Sloppy Mode Semantics (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-22T18-26-3)
1"use strict";
2(function() {
3 try {
4 with ({Math: {pow: (b, e) => b + e}}) {
5 console.log(Math.pow(2, 3));
6 }
7 } catch (e) {
8 console.log(e.name);
9 }
10})();
11(function() {
12 "use strict";
13 try {
14 with ({Math: {pow: (b, e) => b + e}}) {
15 console.log(2 ** 3);
16 }
17 } catch (e) {
18 console.log(e.name);
19 }
20})();
r2-p007 · Comma Expressions in Arguments
1// This line would be invalid if fixed incorrectly
2// Math.pow(1, 2, 3); // Rule does not fire (too many args)
3// 1 ** 2 ** 3; // Would be SyntaxError if mistakenly generated
4console.log('No trigger - comma in args invalid for Math.pow');
No trigger - comma in args invalid for Math.pow
r2-p008 · Temporal Dead Zone After Hoisting
1console.log(typeof base);
2console.log(typeof exp);
3var base = 2;
4var exp = 3;
5console.log(Math.pow(base, exp));
6base = 4;
7exp = 2;
8console.log(base ** exp);
--- orig.js 2026-10-22 14:27:02.739557878 -0400 +++ fixed.js 2026-10-22 14:27:05.358512341 -0400 @@ -2,7 +2,7 @@ console.log(typeof exp); var base = 2; var exp = 3; -console.log(Math.pow(base, exp)); +console.log(base**exp); base = 4; exp = 2; console.log(base ** exp);
undefined undefined 8 16