143 lines
6.4 KiB
Plaintext
143 lines
6.4 KiB
Plaintext
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
const utils_1 = require("@typescript-eslint/utils");
|
|
const util_1 = require("../util");
|
|
const confusingOperators = new Set([
|
|
'=',
|
|
'==',
|
|
'===',
|
|
'in',
|
|
'instanceof',
|
|
]);
|
|
function isConfusingOperator(operator) {
|
|
return confusingOperators.has(operator);
|
|
}
|
|
exports.default = (0, util_1.createRule)({
|
|
name: 'no-confusing-non-null-assertion',
|
|
meta: {
|
|
type: 'problem',
|
|
docs: {
|
|
description: 'Disallow non-null assertion in locations that may be confusing',
|
|
recommended: 'stylistic',
|
|
},
|
|
hasSuggestions: true,
|
|
messages: {
|
|
confusingAssign: 'Confusing combination of non-null assertion and assignment like `a! = b`, which looks very similar to `a != b`.',
|
|
confusingEqual: 'Confusing combination of non-null assertion and equality test like `a! == b`, which looks very similar to `a !== b`.',
|
|
confusingOperator: 'Confusing combination of non-null assertion and `{{operator}}` operator like `a! {{operator}} b`, which might be misinterpreted as `!(a {{operator}} b)`.',
|
|
notNeedInAssign: 'Remove unnecessary non-null assertion (!) in assignment left-hand side.',
|
|
notNeedInEqualTest: 'Remove unnecessary non-null assertion (!) in equality test.',
|
|
notNeedInOperator: 'Remove possibly unnecessary non-null assertion (!) in the left operand of the `{{operator}}` operator.',
|
|
wrapUpLeft: 'Wrap the left-hand side in parentheses to avoid confusion with "{{operator}}" operator.',
|
|
},
|
|
schema: [],
|
|
},
|
|
defaultOptions: [],
|
|
create(context) {
|
|
function confusingOperatorToMessageData(operator) {
|
|
switch (operator) {
|
|
case '=':
|
|
return {
|
|
messageId: 'confusingAssign',
|
|
};
|
|
case '==':
|
|
case '===':
|
|
return {
|
|
messageId: 'confusingEqual',
|
|
};
|
|
case 'in':
|
|
case 'instanceof':
|
|
return {
|
|
messageId: 'confusingOperator',
|
|
data: { operator },
|
|
};
|
|
// istanbul ignore next
|
|
default:
|
|
operator;
|
|
throw new Error(`Unexpected operator ${operator}`);
|
|
}
|
|
}
|
|
return {
|
|
'BinaryExpression, AssignmentExpression'(node) {
|
|
const operator = node.operator;
|
|
if (isConfusingOperator(operator)) {
|
|
// Look for a non-null assertion as the last token on the left hand side.
|
|
// That way, we catch things like `1 + two! === 3`, even though the left
|
|
// hand side isn't a non-null assertion AST node.
|
|
const leftHandFinalToken = context.sourceCode.getLastToken(node.left);
|
|
const tokenAfterLeft = context.sourceCode.getTokenAfter(node.left);
|
|
if (leftHandFinalToken?.type === utils_1.AST_TOKEN_TYPES.Punctuator &&
|
|
leftHandFinalToken.value === '!' &&
|
|
tokenAfterLeft?.value !== ')') {
|
|
if (node.left.type === utils_1.AST_NODE_TYPES.TSNonNullExpression) {
|
|
let suggestions;
|
|
switch (operator) {
|
|
case '=':
|
|
suggestions = [
|
|
{
|
|
messageId: 'notNeedInAssign',
|
|
fix: (fixer) => fixer.remove(leftHandFinalToken),
|
|
},
|
|
];
|
|
break;
|
|
case '==':
|
|
case '===':
|
|
suggestions = [
|
|
{
|
|
messageId: 'notNeedInEqualTest',
|
|
fix: (fixer) => fixer.remove(leftHandFinalToken),
|
|
},
|
|
];
|
|
break;
|
|
case 'in':
|
|
case 'instanceof':
|
|
suggestions = [
|
|
{
|
|
messageId: 'notNeedInOperator',
|
|
data: { operator },
|
|
fix: (fixer) => fixer.remove(leftHandFinalToken),
|
|
},
|
|
{
|
|
messageId: 'wrapUpLeft',
|
|
data: { operator },
|
|
fix: wrapUpLeftFixer(node),
|
|
},
|
|
];
|
|
break;
|
|
// istanbul ignore next
|
|
default:
|
|
operator;
|
|
return;
|
|
}
|
|
context.report({
|
|
node,
|
|
...confusingOperatorToMessageData(operator),
|
|
suggest: suggestions,
|
|
});
|
|
}
|
|
else {
|
|
context.report({
|
|
node,
|
|
...confusingOperatorToMessageData(operator),
|
|
suggest: [
|
|
{
|
|
messageId: 'wrapUpLeft',
|
|
data: { operator },
|
|
fix: wrapUpLeftFixer(node),
|
|
},
|
|
],
|
|
});
|
|
}
|
|
}
|
|
}
|
|
},
|
|
};
|
|
},
|
|
});
|
|
function wrapUpLeftFixer(node) {
|
|
return (fixer) => [
|
|
fixer.insertTextBefore(node.left, '('),
|
|
fixer.insertTextAfter(node.left, ')'),
|
|
];
|
|
}
|