eslint-plugin-unicorn
Version:
More than 100 powerful ESLint rules
139 lines (120 loc) • 3.68 kB
JavaScript
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;