@@ -34,6 +34,7 @@ function isNaNIdentifier(node) {
3434/** @type {import('../shared/types').Rule } */
3535module . exports = {
3636 meta : {
37+ hasSuggestions : true ,
3738 type : "problem" ,
3839
3940 docs : {
@@ -63,14 +64,45 @@ module.exports = {
6364 comparisonWithNaN : "Use the isNaN function to compare with NaN." ,
6465 switchNaN : "'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch." ,
6566 caseNaN : "'case NaN' can never match. Use Number.isNaN before the switch." ,
66- indexOfNaN : "Array prototype method '{{ methodName }}' cannot find NaN."
67+ indexOfNaN : "Array prototype method '{{ methodName }}' cannot find NaN." ,
68+ replaceWithIsNaN : "Replace with Number.isNaN." ,
69+ replaceWithCastingAndIsNaN : "Replace with Number.isNaN cast to a Number."
6770 }
6871 } ,
6972
7073 create ( context ) {
7174
7275 const enforceForSwitchCase = ! context . options [ 0 ] || context . options [ 0 ] . enforceForSwitchCase ;
7376 const enforceForIndexOf = context . options [ 0 ] && context . options [ 0 ] . enforceForIndexOf ;
77+ const sourceCode = context . sourceCode ;
78+
79+ const fixableOperators = new Set ( [ "==" , "===" , "!=" , "!==" ] ) ;
80+ const castableOperators = new Set ( [ "==" , "!=" ] ) ;
81+
82+ /**
83+ * Get a fixer for a binary expression that compares to NaN.
84+ * @param {ASTNode } node The node to fix.
85+ * @param {function(string): string } wrapValue A function that wraps the compared value with a fix.
86+ * @returns {function(Fixer): Fix } The fixer function.
87+ */
88+ function getBinaryExpressionFixer ( node , wrapValue ) {
89+ return fixer => {
90+ const comparedValue = isNaNIdentifier ( node . left ) ? node . right : node . left ;
91+ const shouldWrap = comparedValue . type === "SequenceExpression" ;
92+ const shouldNegate = node . operator [ 0 ] === "!" ;
93+
94+ const negation = shouldNegate ? "!" : "" ;
95+ let comparedValueText = sourceCode . getText ( comparedValue ) ;
96+
97+ if ( shouldWrap ) {
98+ comparedValueText = `(${ comparedValueText } )` ;
99+ }
100+
101+ const fixedValue = wrapValue ( comparedValueText ) ;
102+
103+ return fixer . replaceText ( node , `${ negation } ${ fixedValue } ` ) ;
104+ } ;
105+ }
74106
75107 /**
76108 * Checks the given `BinaryExpression` node for `foo === NaN` and other comparisons.
@@ -82,7 +114,29 @@ module.exports = {
82114 / ^ (?: [ < > ] | [ ! = ] = ) = ? $ / u. test ( node . operator ) &&
83115 ( isNaNIdentifier ( node . left ) || isNaNIdentifier ( node . right ) )
84116 ) {
85- context . report ( { node, messageId : "comparisonWithNaN" } ) ;
117+ const suggestedFixes = [ ] ;
118+ const isFixable = fixableOperators . has ( node . operator ) ;
119+ const isCastable = castableOperators . has ( node . operator ) ;
120+
121+ if ( isFixable ) {
122+ suggestedFixes . push ( {
123+ messageId : "replaceWithIsNaN" ,
124+ fix : getBinaryExpressionFixer ( node , value => `Number.isNaN(${ value } )` )
125+ } ) ;
126+ }
127+
128+ if ( isCastable ) {
129+ suggestedFixes . push ( {
130+ messageId : "replaceWithCastingAndIsNaN" ,
131+ fix : getBinaryExpressionFixer ( node , value => `Number.isNaN(Number(${ value } ))` )
132+ } ) ;
133+ }
134+
135+ context . report ( {
136+ node,
137+ messageId : "comparisonWithNaN" ,
138+ suggest : suggestedFixes
139+ } ) ;
86140 }
87141 }
88142
0 commit comments