β¬ οΈ Back to Table of Contents
π restrict-plus-operands¶
π Analysis Summary¶
| Metric | Count |
|---|---|
| π§ Functions | 5 |
| π¦ Imports | 7 |
| π Type Aliases | 2 |
π Table of Contents¶
π οΈ File Location:¶
π packages/eslint-plugin/src/rules/restrict-plus-operands.ts
π€ Default Export¶
| Property | Value |
|---|---|
name |
'restrict-plus-operands' |
meta.type |
'problem' |
meta.docs.description |
'Require both operands of addition to be the same type and be bigint, number, or string' |
meta.docs.recommended.recommended |
true |
meta.docs.recommended.strict |
[ { allowAny: false, allowBoolean: false, allowNullish: false, allowNumberAndString: false, allowRegExp: false, }, ] |
meta.docs.requiresTypeChecking |
true |
meta.messages.bigintAndNumber |
"Numeric '+' operations must either be both bigints or both numbers. Got {{left}} + {{right}}." |
meta.messages.invalid |
"Invalid operand for a '+' operation. Operands must each be a number or {{stringLike}}. Got {{type}}." |
meta.messages.mismatched |
"Operands of '+' operations must be a number or {{stringLike}}. Got {{left}} + {{right}}." |
meta.schema |
[ { type: 'object', additionalProperties: false, properties: { allowAny: { type: 'boolean', description: 'Whether to ... |
defaultOptions |
[ { allowAny: true, allowBoolean: true, allowNullish: true, allowNumberAndString: true, allowRegExp: true, skipCompou... |
Entry point: create β documented under Functions.
π¦ Imports¶
| Name | Source |
|---|---|
TSESTree |
@typescript-eslint/utils |
createRule |
../util |
getConstrainedTypeAtLocation |
../util |
getParserServices |
../util |
getTypeName |
../util |
isTypeAnyType |
../util |
isTypeFlagSet |
../util |
Functions¶
create(context: any, [ { allowAny, allowBoolean, allβ¦: any): { "AssignmentExpression[operator='+=']"(node: any): void; "⦶
Parameters:
contextany[ { allowAny, allowBoolean, allowNullish, allowNumberAndString, allowRegExp, skipCompoundAssignments, }, ]any
Returns: { "AssignmentExpression[operator='+=']"(node: any): void; "BinaryExpression[operator='+']": (node: TSESTree.AssignmentExpression | TSESTree.BinaryExpression) => void; }
Calls:
getParserServices (from ../util)services.program.getTypeChecker[ allowAny && 'any', allowBoolean && 'boolean', allowNullish && 'null', allowRegExp && 'RegExp', allowNullish && 'undefined', ].filterstringLikes.jointypeChecker.getBaseTypeOfLiteralTypegetConstrainedTypeAtLocation (from ../util)getTypeConstrainedtsutils.isTypeFlagSetisTypeFlagSetInUnionisTypeFlagSet (from ../util)context.reporttypeChecker.typeToStringtsutils.unionConstituentsgetTypeName (from ../util)isTypeAnyType (from ../util)isDeeplyObjectTypecheckPlusOperands
Internal Comments:
Code
create(
context,
[
{
allowAny,
allowBoolean,
allowNullish,
allowNumberAndString,
allowRegExp,
skipCompoundAssignments,
},
],
) {
const services = getParserServices(context);
const typeChecker = services.program.getTypeChecker();
const stringLikes = [
allowAny && '`any`',
allowBoolean && '`boolean`',
allowNullish && '`null`',
allowRegExp && '`RegExp`',
allowNullish && '`undefined`',
].filter((value): value is string => typeof value === 'string');
const stringLike = stringLikes.length
? stringLikes.length === 1
? `string, allowing a string + ${stringLikes[0]}`
: `string, allowing a string + any of: ${stringLikes.join(', ')}`
: 'string';
function getTypeConstrained(node: TSESTree.Node): ts.Type {
return typeChecker.getBaseTypeOfLiteralType(
getConstrainedTypeAtLocation(services, node),
);
}
function checkPlusOperands(
node: TSESTree.AssignmentExpression | TSESTree.BinaryExpression,
): void {
const leftType = getTypeConstrained(node.left);
const rightType = getTypeConstrained(node.right);
if (
leftType === rightType &&
tsutils.isTypeFlagSet(
leftType,
ts.TypeFlags.BigIntLike |
ts.TypeFlags.NumberLike |
ts.TypeFlags.StringLike,
)
) {
return;
}
let hadIndividualComplaint = false;
for (const [baseNode, baseType, otherType] of [
[node.left, leftType, rightType],
[node.right, rightType, leftType],
] as const) {
if (
isTypeFlagSetInUnion(
baseType,
ts.TypeFlags.ESSymbolLike |
ts.TypeFlags.Never |
ts.TypeFlags.Unknown,
) ||
(!allowAny && isTypeFlagSetInUnion(baseType, ts.TypeFlags.Any)) ||
(!allowBoolean &&
isTypeFlagSetInUnion(baseType, ts.TypeFlags.BooleanLike)) ||
(!allowNullish &&
isTypeFlagSet(baseType, ts.TypeFlags.Null | ts.TypeFlags.Undefined))
) {
context.report({
node: baseNode,
messageId: 'invalid',
data: {
type: typeChecker.typeToString(baseType),
stringLike,
},
});
hadIndividualComplaint = true;
continue;
}
// RegExps also contain ts.TypeFlags.Any & ts.TypeFlags.Object
for (const subBaseType of tsutils.unionConstituents(baseType)) {
const typeName = getTypeName(typeChecker, subBaseType);
if (
typeName === 'RegExp'
? !allowRegExp ||
tsutils.isTypeFlagSet(otherType, ts.TypeFlags.NumberLike)
: (!allowAny && isTypeAnyType(subBaseType)) ||
isDeeplyObjectType(subBaseType)
) {
context.report({
node: baseNode,
messageId: 'invalid',
data: {
type: typeChecker.typeToString(subBaseType),
stringLike,
},
});
hadIndividualComplaint = true;
continue;
}
}
}
if (hadIndividualComplaint) {
return;
}
for (const [baseType, otherType] of [
[leftType, rightType],
[rightType, leftType],
] as const) {
if (
!allowNumberAndString &&
isTypeFlagSetInUnion(baseType, ts.TypeFlags.StringLike) &&
isTypeFlagSetInUnion(
otherType,
ts.TypeFlags.NumberLike | ts.TypeFlags.BigIntLike,
)
) {
return context.report({
node,
messageId: 'mismatched',
data: {
left: typeChecker.typeToString(leftType),
right: typeChecker.typeToString(rightType),
stringLike,
},
});
}
if (
isTypeFlagSetInUnion(baseType, ts.TypeFlags.NumberLike) &&
isTypeFlagSetInUnion(otherType, ts.TypeFlags.BigIntLike)
) {
return context.report({
node,
messageId: 'bigintAndNumber',
data: {
left: typeChecker.typeToString(leftType),
right: typeChecker.typeToString(rightType),
},
});
}
}
}
return {
"BinaryExpression[operator='+']": checkPlusOperands,
...(!skipCompoundAssignments && {
"AssignmentExpression[operator='+=']"(node): void {
checkPlusOperands(node);
},
}),
};
}
isDeeplyObjectType(type: ts.Type): boolean¶
Parameters:
typets.Type
Returns: boolean
Calls:
type.isIntersectiontsutils.intersectionConstituents(type).everytsutils.unionConstituents(type).every
Code
isTypeFlagSetInUnion(type: ts.Type, flag: ts.TypeFlags): boolean¶
Parameters:
typets.Typeflagts.TypeFlags
Returns: boolean
Calls:
tsutils .unionConstituents(type) .sometsutils.isTypeFlagSet
Code
Internal helpers¶
Declared inside another function in this file.
getTypeConstrained(node: TSESTree.Node): ts.Type¶
Parameters:
nodeTSESTree.Node
Returns: ts.Type
Calls:
typeChecker.getBaseTypeOfLiteralTypegetConstrainedTypeAtLocation (from ../util)
Code
checkPlusOperands(node: TSESTree.AssignmentExpression | TSESTreβ¦): void¶
Parameters:
nodeTSESTree.AssignmentExpression | TSESTree.BinaryExpression
Returns: void
Calls:
getTypeConstrainedtsutils.isTypeFlagSetisTypeFlagSetInUnionisTypeFlagSet (from ../util)context.reporttypeChecker.typeToStringtsutils.unionConstituentsgetTypeName (from ../util)isTypeAnyType (from ../util)isDeeplyObjectType
Internal Comments:
Code
function checkPlusOperands(
node: TSESTree.AssignmentExpression | TSESTree.BinaryExpression,
): void {
const leftType = getTypeConstrained(node.left);
const rightType = getTypeConstrained(node.right);
if (
leftType === rightType &&
tsutils.isTypeFlagSet(
leftType,
ts.TypeFlags.BigIntLike |
ts.TypeFlags.NumberLike |
ts.TypeFlags.StringLike,
)
) {
return;
}
let hadIndividualComplaint = false;
for (const [baseNode, baseType, otherType] of [
[node.left, leftType, rightType],
[node.right, rightType, leftType],
] as const) {
if (
isTypeFlagSetInUnion(
baseType,
ts.TypeFlags.ESSymbolLike |
ts.TypeFlags.Never |
ts.TypeFlags.Unknown,
) ||
(!allowAny && isTypeFlagSetInUnion(baseType, ts.TypeFlags.Any)) ||
(!allowBoolean &&
isTypeFlagSetInUnion(baseType, ts.TypeFlags.BooleanLike)) ||
(!allowNullish &&
isTypeFlagSet(baseType, ts.TypeFlags.Null | ts.TypeFlags.Undefined))
) {
context.report({
node: baseNode,
messageId: 'invalid',
data: {
type: typeChecker.typeToString(baseType),
stringLike,
},
});
hadIndividualComplaint = true;
continue;
}
// RegExps also contain ts.TypeFlags.Any & ts.TypeFlags.Object
for (const subBaseType of tsutils.unionConstituents(baseType)) {
const typeName = getTypeName(typeChecker, subBaseType);
if (
typeName === 'RegExp'
? !allowRegExp ||
tsutils.isTypeFlagSet(otherType, ts.TypeFlags.NumberLike)
: (!allowAny && isTypeAnyType(subBaseType)) ||
isDeeplyObjectType(subBaseType)
) {
context.report({
node: baseNode,
messageId: 'invalid',
data: {
type: typeChecker.typeToString(subBaseType),
stringLike,
},
});
hadIndividualComplaint = true;
continue;
}
}
}
if (hadIndividualComplaint) {
return;
}
for (const [baseType, otherType] of [
[leftType, rightType],
[rightType, leftType],
] as const) {
if (
!allowNumberAndString &&
isTypeFlagSetInUnion(baseType, ts.TypeFlags.StringLike) &&
isTypeFlagSetInUnion(
otherType,
ts.TypeFlags.NumberLike | ts.TypeFlags.BigIntLike,
)
) {
return context.report({
node,
messageId: 'mismatched',
data: {
left: typeChecker.typeToString(leftType),
right: typeChecker.typeToString(rightType),
stringLike,
},
});
}
if (
isTypeFlagSetInUnion(baseType, ts.TypeFlags.NumberLike) &&
isTypeFlagSetInUnion(otherType, ts.TypeFlags.BigIntLike)
) {
return context.report({
node,
messageId: 'bigintAndNumber',
data: {
left: typeChecker.typeToString(leftType),
right: typeChecker.typeToString(rightType),
},
});
}
}
}
Type Aliases¶
Options¶
type Options = [
{
allowAny?: boolean;
allowBoolean?: boolean;
allowNullish?: boolean;
allowNumberAndString?: boolean;
allowRegExp?: boolean;
skipCompoundAssignments?: boolean;
},
];
MessageIds¶
Generated by Syntax Scribe