UNPKG

eslint-plugin-unicorn

Version:
143 lines (124 loc) 3.54 kB
import {isLiteral} from './ast/index.js'; import { addParenthesesToReturnOrThrowExpression, removeSpacesAfter, } from './fix/index.js'; import { needsSemicolon, isParenthesized, isOnSameLine, isGlobalIdentifier, } from './utils/index.js'; const MESSAGE_ID_ERROR = 'no-typeof-undefined/error'; const MESSAGE_ID_SUGGESTION = 'no-typeof-undefined/suggestion'; const messages = { [MESSAGE_ID_ERROR]: 'Compare with `undefined` directly instead of using `typeof`.', [MESSAGE_ID_SUGGESTION]: 'Switch to `… {{operator}} undefined`.', }; /** @param {import('eslint').Rule.RuleContext} context */ const create = context => { const {checkGlobalVariables} = context.options[0]; context.on('BinaryExpression', binaryExpression => { if (!( ( [ '===', '!==', '==', '!=', ].includes(binaryExpression.operator) ) && binaryExpression.left.type === 'UnaryExpression' && binaryExpression.left.operator === 'typeof' && binaryExpression.left.prefix && isLiteral(binaryExpression.right, 'undefined') )) { return; } const {left: typeofNode, right: undefinedString, operator} = binaryExpression; const {sourceCode} = context; const valueNode = typeofNode.argument; const isGlobalVariable = isGlobalIdentifier(valueNode, context); if (!checkGlobalVariables && isGlobalVariable) { return; } const [typeofToken, secondToken] = sourceCode.getFirstTokens(typeofNode, 2); const fix = function * (fixer) { // Change `==`/`!=` to `===`/`!==` if (operator === '==' || operator === '!=') { const operatorToken = sourceCode.getTokenAfter( typeofNode, token => token.type === 'Punctuator' && token.value === operator, ); yield fixer.insertTextAfter(operatorToken, '='); } yield fixer.replaceText(undefinedString, 'undefined'); yield fixer.remove(typeofToken); yield removeSpacesAfter(typeofToken, context, fixer); const {parent} = binaryExpression; if ( (parent.type === 'ReturnStatement' || parent.type === 'ThrowStatement') && parent.argument === binaryExpression && !isOnSameLine(typeofToken, secondToken, context) && !isParenthesized(binaryExpression, context) && !isParenthesized(typeofNode, context) ) { yield addParenthesesToReturnOrThrowExpression(fixer, parent, context); return; } const tokenBefore = sourceCode.getTokenBefore(binaryExpression); if (needsSemicolon(tokenBefore, context, secondToken.value)) { yield fixer.insertTextBefore(binaryExpression, ';'); } }; const problem = { node: binaryExpression, loc: sourceCode.getLoc(typeofToken), messageId: MESSAGE_ID_ERROR, }; if (isGlobalVariable) { problem.suggest = [ { messageId: MESSAGE_ID_SUGGESTION, data: {operator: operator.startsWith('!') ? '!==' : '==='}, fix, }, ]; } else { problem.fix = fix; } return problem; }); }; const schema = [ { type: 'object', additionalProperties: false, properties: { checkGlobalVariables: { type: 'boolean', description: 'Whether to also check `typeof` comparisons against global variables.', }, }, }, ]; /** @type {import('eslint').Rule.RuleModule} */ const config = { create, meta: { type: 'suggestion', docs: { description: 'Disallow comparing `undefined` using `typeof`.', recommended: 'unopinionated', }, fixable: 'code', hasSuggestions: true, schema, defaultOptions: [{checkGlobalVariables: false}], messages, languages: [ 'js/js', ], }, }; export default config;