eslint-rules/strictly-null.js (102 lines of code) (raw):

/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @format */ 'use strict'; const messages = { WEAK_NULL: 'Always use `== null` or `!= null` to check for `null` AND `undefined` values (even if you just expect either of them). Within fb we treat them as equal and `== null` checks for both.', CHECK_NULL: 'Use `== null` or `!= null` instead of `undefined` or `void 0` as it checks for both anyways', }; /** * A lint rule to require `null` and `undefined` checks to be `== null` (or !=) * Enforces that all `undefined` and `null` checks use `== null` / `!= null` */ function rule(context) { const sourceCode = context.getSourceCode(); return { BinaryExpression(node) { if (node.operator === '===' || node.operator === '!==') { if (isTargetedNode(node.left)) { reportStrict( node, node.right, sourceCode.getTokenAfter(node.left), node.left, ); } else if (isTargetedNode(node.right)) { reportStrict( node, node.left, sourceCode.getTokenAfter(node.left), node.right, ); } } else if (node.operator === '==' || node.operator === '!=') { if (isUndefinedNode(node.left) || isVoidNode(node.left)) { reportWeak( node, node.right, sourceCode.getTokenAfter(node.left), node.left, ); } else if (isUndefinedNode(node.right) || isVoidNode(node.right)) { reportWeak( node, node.left, sourceCode.getTokenAfter(node.left), node.right, ); } } }, }; function reportStrict(parent, childToKeep, eqToken, childToDitch) { context.report({ node: parent, message: messages.WEAK_NULL, fix: createAutofixer(parent, childToKeep, eqToken, childToDitch), }); } function reportWeak(parent, childToKeep, eqToken, childToDitch) { context.report({ node: parent, message: messages.CHECK_NULL, fix: createAutofixer(parent, childToKeep, eqToken, childToDitch), }); } function createAutofixer(parent, childToKeep, eqToken, childToDitch) { // If the node was wrapped in a group then that won't show up here // so make sure to skip past the group-closing tokens first. while (eqToken.value === ')') { eqToken = sourceCode.getTokenAfter(eqToken); } if ( eqToken.value !== '==' && eqToken.value !== '===' && eqToken.value !== '!=' && eqToken.value !== '!==' ) { // Unexpected token value. Returning to prevent accidental clobbering. return null; } // Note: make sure `(a||b)===null` does not become `a||b==null` ! return fixer => [ fixer.replaceText( eqToken, eqToken.value === '===' || eqToken.value === '==' ? '==' : '!=', ), fixer.replaceText(childToDitch, 'null'), ]; } } function isTargetedNode(node) { return isNullNode(node) || isUndefinedNode(node) || isVoidNode(node); } function isNullNode(node) { return node.type === 'Literal' && node.value == null; } function isUndefinedNode(node) { return node.type === 'Identifier' && node.name === 'undefined'; } function isVoidNode(node) { return ( node.type === 'UnaryExpression' && node.operator === 'void' && node.argument.type === 'Literal' && node.argument.value === 0 ); } rule.messages = messages; rule.schema = []; // none, but mandatory field rule.meta = {fixable: 'code'}; module.exports = rule;