eslint no-implicit-coercion
labelled safe
Analysis Nemotron 3 Super
Flags implicit type coercions like !!expr, +expr, expr+'', etc., suggesting explicit Boolean(), Number(), String() calls.
Fix: Rewrites implicit coercions to explicit constructor calls: !!expr → Boolean(expr), +expr/-expr patterns → Number(expr), ''+expr/template literals → String(expr), and compound assignments like foo+= '' → foo = String(foo).
9 hazards
- The fix assumes expr has no side effects; if expr is a function call or property access with side effects, duplicating it in the fix (e.g., Number(foo()) vs original +foo()) changes evaluation count.
- If expr evaluates to an object with a custom valueOf/toString, the implicit coercion may trigger those methods differently than the explicit constructor (though ES6+ spec aligns them, edge cases exist in proxy objects).
- In expressions like foo += '', the fix foo = String(foo) changes the assignment target if foo is a complex lhs (e.g., obj.prop += '' becomes obj.prop = String(obj.prop), which is safe, but if foo is a dereference like arr[0] += '', it remains safe; however, if the lhs involves getters/setters, the fix may invoke the setter once instead of twice (get + set vs just set).
- The fix removes any whitespace or formatting from the original expression (e.g., + foo becomes Number(foo)), which is harmless but strips user formatting.
- If the original code relied on the implicit coercion producing NaN or 0 for non-numeric inputs (e.g., +undefined), the fix behaves identically, so no hazard here—this is actually safe.
- In rare cases involving @@toPrimitive symbols, the order of hint preference between implicit and explicit coercion could differ pre-ES2015, but modern engines unify this.
- If the expression is wrapped in parentheses with a comment (e.g., +(/* hint */foo)), the fix may drop the comment during rewrite, losing developer intent.
- When the coerced result is used immediately in a larger expression (e.g., +foo + 1), the fix Number(foo) + 1 preserves precedence and behavior, so no hazard.
- If the variable foo is const and the fix attempts to reassign it (only relevant for foo += '' cases), the linter would not suggest the fix for const variables, so this hazard does not apply—the tool correctly avoids proposing invalid fixes.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | silent prediction off | __proto__ keys and property access interaction | 1. __proto__ keys, prototype access, Object.assign vs spread |
| r1-p002 | fired, no fix | Parenthesized expression with inline comment losing comment after fix | 2. Parenthesized expression with comment may lose comment during rewrite |
| r1-p003 | fired, no fix | Compound assignment with getter-setter side effect counting | 3. Compound assignment lhs with getters/setters may invoke setter once instead of twice |
| r1-p004 | fired, no fix prediction off | Non-numeric input coercion safety check (+undefined) | 4. Implicit coercion producing NaN/0 for non-numeric inputs is safe under fix |
| r1-p005 | fired, no fix | Function declaration inside block scoped by if statement | 5. Function declarations inside blocks, switch cases, labeled statements |
| r1-p006 | fired, no fix | This binding in extracted method called via call() | 6. This binding: arrow vs function, methods extracted from objects, call/apply/bind |
| r1-p007 | fired, no fix | Labelled break with continue in nested loop interacting with coerced value | 7. Label/break/continue control flow, finally blocks, early returns in nested blocks |
| r1-p008 | fired, no fix | Getter invocation count change due to rewrite of +getter | 8. Getters, setters and Object.defineProperty side effects |
| r2-p001 | fired, no fix | Custom valueOf/toString in Proxy breaks equivalence | If expr evaluates to an object with a custom valueOf/toString, the implicit coercion may trigger those methods differently than the explicit constructor. |
| r2-p002 | fired, no fix | Side effect duplication in function call | The fix assumes expr has no side effects; if expr is a function call or property access with side effects, duplicating it in the fix (e.g., Number(foo()) vs original +foo()) changes evaluation count. |
| r2-p003 | fired, no fix | __proto__ key interaction with assignment | __proto__ keys, prototype access, Object.assign vs spread |
| r2-p004 | fired, no fix | Sparse array hole in implicit coercion context | sparse arrays, holes, Array(n) vs [n], array-likes |
| r2-p005 | fired, no fix | ASI hazard with leading parenthesis | ASI and semicolon-less code, leading ( or [ on the next line |
| r2-p006 | fired, no fix prediction off | Coerced result used in larger expression (control) | When the coerced result is used immediately in a larger expression (e.g., +foo + 1), the fix Number(foo) + 1 preserves precedence and behavior, so no hazard. |
| r2-p007 | fired, no fix | Var hoisting and re-declaration in loop | var hoisting and re-declaration inside loops or after use |
| r2-p008 | fired, no fix | Comment attached to moved code affects readability but not semantics | comments attached to the removed or moved code |
Sources
r1-p001 · __proto__ keys and property access interaction
1let o = {a: 1}; let p = {}; p.__proto__ = o;
2function f(x){ return x + 0; }
3console.log(f(p.a));
1
r1-p002 · Parenthesized expression with inline comment losing comment after fix
1let x = 5;
2let y = +(/* keep this */x);
3console.log(typeof y, y);
number 5
r1-p003 · Compound assignment with getter-setter side effect counting
1let val = 0;
2const obj = {
3 get prop(){ return val; },
4 set prop(v){ val = v; log.push('set:' + v); }
5};
6let log = [];
7obj.prop += '';
8console.log(log.join('|'), val);
set:0 0
r1-p004 · Non-numeric input coercion safety check (+undefined)
1let u;
2console.log(typeof +u, +u);
3console.log(typeof Number(u), Number(u));
number NaN number NaN
r1-p005 · Function declaration inside block scoped by if statement
1if(true){
2 function f(){ return +''; }
3 console.log(f());
4}
5console.log(typeof f);
0 function
r1-p006 · This binding in extracted method called via call()
1const obj = {val: 42, getNum(){ return +this.val; }};
2const fn = obj.getNum;
3try{ console.log(fn.call(obj)); }catch(e){ console.log(e.name); }
42
r1-p007 · Labelled break with continue in nested loop interacting with coerced value
1outer: for(let i=0;i<2;i++){
2 for(let j=0;j<2;j++){
3 if(j===1) continue outer;
4 let x = +'';
5 console.log(i+j+x);
6 }
7}
0 1
r1-p008 · Getter invocation count change due to rewrite of +getter
1let count = 0;
2let obj = {};
3Object.defineProperty(obj, 'x', {
4 get(){ count++; return 5; }
5});
6console.log(+obj.x, count);
5 1
r2-p001 · Custom valueOf/toString in Proxy breaks equivalence
1let obj = new Proxy({ valueOf: () => { console.log('valueOf'); return 42; }, toString: () => { console.log('toString'); return '42'; } }, {});
2console.log(+obj);
3console.log(Boolean(obj));
valueOf 42 true
r2-p002 · Side effect duplication in function call
1let count = 0;
2function foo() { count++; return '5';
3}
4console.log(+foo());
5console.log(count);
5 1
r2-p003 · __proto__ key interaction with assignment
1let o = {};
2o['__proto__'] = { valueOf: () => 99 };
3console.log(+o);
99
r2-p004 · Sparse array hole in implicit coercion context
1let arr = Array(1);
2arr[0] = '5';
3console.log(+arr);
5
r2-p005 · ASI hazard with leading parenthesis
1let x = 1
2(+x).toString()
3console.log(x)
r2-p006 · Coerced result used in larger expression (control)
1let foo = '5';
2console.log(+foo + 1);
6
r2-p007 · Var hoisting and re-declaration in loop
1for (var i = 0; i < 1; i++) {
2 var x = '10';
3 console.log(+x);
4}
5console.log(typeof x);
10 string
r2-p008 · Comment attached to moved code affects readability but not semantics
1let y = '7' // inline comment
2;console.log(+y); // another
7