UNPKG

eslint-plugin-unicorn

Version:
139 lines (120 loc) 3.68 kB
import {isLiteral} from './ast/index.js'; import { addParenthesizesToReturnOrThrowExpression, removeSpacesAfter, } from './fix/index.js'; import { needsSemicolon, isParenthesized, isOnSameLine, isUnresolvedVariable, } 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 (!( ( binaryExpression.operator === '===' || binaryExpression.operator === '!==' || binaryExpression.operator === '==' || 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 = valueNode.type === 'Identifier' && (sourceCode.isGlobalReference(valueNode) || isUnresolvedVariable(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 addParenthesizesToReturnOrThrowExpression(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, }, }; export default config;