142 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			142 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * @fileoverview Rule to flag comparisons to the value NaN
 | |
|  * @author James Allardice
 | |
|  */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| // Requirements
 | |
| //------------------------------------------------------------------------------
 | |
| 
 | |
| const astUtils = require("./utils/ast-utils");
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| // Helpers
 | |
| //------------------------------------------------------------------------------
 | |
| 
 | |
| /**
 | |
|  * Determines if the given node is a NaN `Identifier` node.
 | |
|  * @param {ASTNode|null} node The node to check.
 | |
|  * @returns {boolean} `true` if the node is 'NaN' identifier.
 | |
|  */
 | |
| function isNaNIdentifier(node) {
 | |
|     return Boolean(node) && (
 | |
|         astUtils.isSpecificId(node, "NaN") ||
 | |
|         astUtils.isSpecificMemberAccess(node, "Number", "NaN")
 | |
|     );
 | |
| }
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| // Rule Definition
 | |
| //------------------------------------------------------------------------------
 | |
| 
 | |
| /** @type {import('../shared/types').Rule} */
 | |
| module.exports = {
 | |
|     meta: {
 | |
|         type: "problem",
 | |
| 
 | |
|         docs: {
 | |
|             description: "Require calls to `isNaN()` when checking for `NaN`",
 | |
|             recommended: true,
 | |
|             url: "https://eslint.org/docs/latest/rules/use-isnan"
 | |
|         },
 | |
| 
 | |
|         schema: [
 | |
|             {
 | |
|                 type: "object",
 | |
|                 properties: {
 | |
|                     enforceForSwitchCase: {
 | |
|                         type: "boolean",
 | |
|                         default: true
 | |
|                     },
 | |
|                     enforceForIndexOf: {
 | |
|                         type: "boolean",
 | |
|                         default: false
 | |
|                     }
 | |
|                 },
 | |
|                 additionalProperties: false
 | |
|             }
 | |
|         ],
 | |
| 
 | |
|         messages: {
 | |
|             comparisonWithNaN: "Use the isNaN function to compare with NaN.",
 | |
|             switchNaN: "'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch.",
 | |
|             caseNaN: "'case NaN' can never match. Use Number.isNaN before the switch.",
 | |
|             indexOfNaN: "Array prototype method '{{ methodName }}' cannot find NaN."
 | |
|         }
 | |
|     },
 | |
| 
 | |
|     create(context) {
 | |
| 
 | |
|         const enforceForSwitchCase = !context.options[0] || context.options[0].enforceForSwitchCase;
 | |
|         const enforceForIndexOf = context.options[0] && context.options[0].enforceForIndexOf;
 | |
| 
 | |
|         /**
 | |
|          * Checks the given `BinaryExpression` node for `foo === NaN` and other comparisons.
 | |
|          * @param {ASTNode} node The node to check.
 | |
|          * @returns {void}
 | |
|          */
 | |
|         function checkBinaryExpression(node) {
 | |
|             if (
 | |
|                 /^(?:[<>]|[!=]=)=?$/u.test(node.operator) &&
 | |
|                 (isNaNIdentifier(node.left) || isNaNIdentifier(node.right))
 | |
|             ) {
 | |
|                 context.report({ node, messageId: "comparisonWithNaN" });
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Checks the discriminant and all case clauses of the given `SwitchStatement` node for `switch(NaN)` and `case NaN:`
 | |
|          * @param {ASTNode} node The node to check.
 | |
|          * @returns {void}
 | |
|          */
 | |
|         function checkSwitchStatement(node) {
 | |
|             if (isNaNIdentifier(node.discriminant)) {
 | |
|                 context.report({ node, messageId: "switchNaN" });
 | |
|             }
 | |
| 
 | |
|             for (const switchCase of node.cases) {
 | |
|                 if (isNaNIdentifier(switchCase.test)) {
 | |
|                     context.report({ node: switchCase, messageId: "caseNaN" });
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Checks the given `CallExpression` node for `.indexOf(NaN)` and `.lastIndexOf(NaN)`.
 | |
|          * @param {ASTNode} node The node to check.
 | |
|          * @returns {void}
 | |
|          */
 | |
|         function checkCallExpression(node) {
 | |
|             const callee = astUtils.skipChainExpression(node.callee);
 | |
| 
 | |
|             if (callee.type === "MemberExpression") {
 | |
|                 const methodName = astUtils.getStaticPropertyName(callee);
 | |
| 
 | |
|                 if (
 | |
|                     (methodName === "indexOf" || methodName === "lastIndexOf") &&
 | |
|                     node.arguments.length === 1 &&
 | |
|                     isNaNIdentifier(node.arguments[0])
 | |
|                 ) {
 | |
|                     context.report({ node, messageId: "indexOfNaN", data: { methodName } });
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         const listeners = {
 | |
|             BinaryExpression: checkBinaryExpression
 | |
|         };
 | |
| 
 | |
|         if (enforceForSwitchCase) {
 | |
|             listeners.SwitchStatement = checkSwitchStatement;
 | |
|         }
 | |
| 
 | |
|         if (enforceForIndexOf) {
 | |
|             listeners.CallExpression = checkCallExpression;
 | |
|         }
 | |
| 
 | |
|         return listeners;
 | |
|     }
 | |
| };
 |