📄 no-unnecessary-condition.ts
¶
📊 Analysis Summary¶
Metric | Count |
---|---|
🔧 Functions | 37 |
📦 Imports | 22 |
📊 Variables & Constants | 18 |
📑 Type Aliases | 5 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 packages/eslint-plugin/src/rules/no-unnecessary-condition.ts
📦 Imports¶
Name | Source |
---|---|
TSESTree |
@typescript-eslint/utils |
AST_NODE_TYPES |
@typescript-eslint/utils |
AST_TOKEN_TYPES |
@typescript-eslint/utils |
createRule |
../util |
getConstrainedTypeAtLocation |
../util |
getConstraintInfo |
../util |
getParserServices |
../util |
getTypeName |
../util |
getTypeOfPropertyOfName |
../util |
getValueOfLiteralType |
../util |
isArrayMethodCallWithPredicate |
../util |
isIdentifier |
../util |
isNullableType |
../util |
isPossiblyFalsy |
../util |
isPossiblyTruthy |
../util |
isTypeAnyType |
../util |
isTypeFlagSet |
../util |
isTypeUnknownType |
../util |
nullThrows |
../util |
NullThrowsReasons |
../util |
findTruthinessAssertedArgument |
../util/assertionFunctionUtils |
findTypeGuardAssertedArgument |
../util/assertionFunctionUtils |
Variables & Constants¶
Name | Type | Kind | Value | Exported |
---|---|---|---|---|
nullishFlag |
number |
const | ts.TypeFlags.Undefined | ts.TypeFlags.Null |
✗ |
BOOL_OPERATORS |
Set<"==" | "===" | "<" | ">" | "<=" | ">=" | "!=" | "!=="> |
const | `new Set([ | |
'<', | ||||
'>', | ||||
'<=', | ||||
'>=', | ||||
'==', | ||||
'===', | ||||
'!=', | ||||
'!==', | ||||
] as const)` | ✗ | |||
constantLoopConditionsAllowedLiterals |
Set<unknown> |
const | `new Set |
|
true, | ||||
false, | ||||
1, | ||||
0, | ||||
])` | ✗ | |||
property |
any |
const | node.property |
✗ |
messageId |
MessageId | null |
let/var | null |
✗ |
messageId |
MessageId | null |
let/var | null |
✗ |
UNDEFINED |
any |
const | ts.TypeFlags.Undefined |
✗ |
NULL |
any |
const | ts.TypeFlags.Null |
✗ |
VOID |
any |
const | ts.TypeFlags.Void |
✗ |
callback |
any |
const | node.arguments[0] |
✗ |
callbackBody |
any |
const | callback.body.body |
✗ |
hasFalsyReturnTypes |
boolean |
let/var | false |
✗ |
hasTruthyReturnTypes |
boolean |
let/var | false |
✗ |
lhsNode |
any |
const | node.type === AST_NODE_TYPES.CallExpression ? node.callee : node.object |
✗ |
property |
any |
const | node.property |
✗ |
isStringTypeName |
boolean |
const | getTypeName(checker, info.keyType) === 'string' |
✗ |
isOwnNullable |
boolean |
const | `node.type === AST_NODE_TYPES.MemberExpression | |
? !isMemberExpressionNullableOriginFromObject(node) | ||||
: node.type === AST_NODE_TYPES.CallExpression | ||||
? !isCallExpressionNullableOriginFromCallee(node) | ||||
: true` | ✗ | |||
nodeToCheck |
any |
const | node.type === AST_NODE_TYPES.CallExpression ? node.callee : node.object |
✗ |
Functions¶
isNullishType(type: ts.Type): boolean
¶
Code
- Parameters:
type: ts.Type
- Return Type:
boolean
- Calls:
tsutils.isTypeFlagSet
isAlwaysNullish(type: ts.Type): boolean
¶
Code
- Parameters:
type: ts.Type
- Return Type:
boolean
- Calls:
tsutils.unionConstituents(type).every
isPossiblyNullish(type: ts.Type): boolean
¶
Code
-
JSDoc:
-
Parameters:
type: ts.Type
- Return Type:
boolean
- Calls:
tsutils.unionConstituents(type).some
`toStaticValue(type: ts.Type): |¶
| undefined`
Code
function toStaticValue(
type: ts.Type,
):
| { value: bigint | boolean | number | string | null | undefined }
| undefined {
// type.isLiteral() only covers numbers/bigints and strings, hence the rest of the branches.
if (tsutils.isBooleanLiteralType(type)) {
return { value: tsutils.isTrueLiteralType(type) };
}
if (type.flags === ts.TypeFlags.Undefined) {
return { value: undefined };
}
if (type.flags === ts.TypeFlags.Null) {
return { value: null };
}
if (type.isLiteral()) {
return { value: getValueOfLiteralType(type) };
}
return undefined;
}
- Parameters:
type: ts.Type
- Return Type:
| { value: bigint | boolean | number | string | null | undefined } | undefined
- Calls:
tsutils.isBooleanLiteralType
tsutils.isTrueLiteralType
type.isLiteral
getValueOfLiteralType (from ../util)
- Internal Comments:
isBoolOperator(operator: string): operator is BoolOperator
¶
Code
- Parameters:
operator: string
- Return Type:
operator is BoolOperator
- Calls:
(BOOL_OPERATORS as Set<string>).has
booleanComparison(left: unknown, operator: BoolOperator, right: unknown): boolean
¶
Code
function booleanComparison(
left: unknown,
operator: BoolOperator,
right: unknown,
): boolean {
switch (operator) {
case '!=':
// eslint-disable-next-line eqeqeq -- intentionally comparing with loose equality
return left != right;
case '!==':
return left !== right;
case '<':
// @ts-expect-error: we don't care if the comparison seems unintentional.
return left < right;
case '<=':
// @ts-expect-error: we don't care if the comparison seems unintentional.
return left <= right;
case '==':
// eslint-disable-next-line eqeqeq -- intentionally comparing with loose equality
return left == right;
case '===':
return left === right;
case '>':
// @ts-expect-error: we don't care if the comparison seems unintentional.
return left > right;
case '>=':
// @ts-expect-error: we don't care if the comparison seems unintentional.
return left >= right;
}
}
- Parameters:
left: unknown
operator: BoolOperator
right: unknown
- Return Type:
boolean
- Internal Comments:
nodeIsArrayType(node: TSESTree.Expression): boolean
¶
Code
- Parameters:
node: TSESTree.Expression
- Return Type:
boolean
- Calls:
getConstrainedTypeAtLocation (from ../util)
tsutils .unionConstituents(nodeType) .some
checker.isArrayType
nodeIsTupleType(node: TSESTree.Expression): boolean
¶
Code
- Parameters:
node: TSESTree.Expression
- Return Type:
boolean
- Calls:
getConstrainedTypeAtLocation (from ../util)
tsutils .unionConstituents(nodeType) .some
checker.isTupleType
isArrayIndexExpression(node: TSESTree.Expression): boolean
¶
Code
function isArrayIndexExpression(node: TSESTree.Expression): boolean {
return (
// Is an index signature
node.type === AST_NODE_TYPES.MemberExpression &&
node.computed &&
// ...into an array type
(nodeIsArrayType(node.object) ||
// ... or a tuple type
(nodeIsTupleType(node.object) &&
// Exception: literal index into a tuple - will have a sound type
node.property.type !== AST_NODE_TYPES.Literal))
);
}
- Parameters:
node: TSESTree.Expression
- Return Type:
boolean
- Calls:
nodeIsArrayType
nodeIsTupleType
- Internal Comments:
isConditionalAlwaysNecessary(type: ts.Type): boolean
¶
Code
- Parameters:
type: ts.Type
- Return Type:
boolean
- Calls:
tsutils .unionConstituents(type) .some
isTypeAnyType (from ../util)
isTypeUnknownType (from ../util)
isTypeFlagSet (from ../util)
isNullableMemberExpression(node: TSESTree.MemberExpression): boolean
¶
Code
function isNullableMemberExpression(
node: TSESTree.MemberExpression,
): boolean {
const objectType = services.getTypeAtLocation(node.object);
if (node.computed) {
const propertyType = services.getTypeAtLocation(node.property);
return isNullablePropertyType(objectType, propertyType);
}
const property = node.property;
// Get the actual property name, to account for private properties (this.#prop).
const propertyName = context.sourceCode.getText(property);
const propertyType = objectType
.getProperties()
.find(prop => prop.name === propertyName);
if (
propertyType &&
tsutils.isSymbolFlagSet(propertyType, ts.SymbolFlags.Optional)
) {
return true;
}
return false;
}
- Parameters:
node: TSESTree.MemberExpression
- Return Type:
boolean
- Calls:
services.getTypeAtLocation
isNullablePropertyType
context.sourceCode.getText
objectType .getProperties() .find
tsutils.isSymbolFlagSet
- Internal Comments:
checkNode(expression: TSESTree.Expression, isUnaryNotArgument: boolean, node: TSESTree.Expression): void
¶
Code
function checkNode(
expression: TSESTree.Expression,
isUnaryNotArgument = false,
node = expression,
): void {
// Check if the node is Unary Negation expression and handle it
if (
expression.type === AST_NODE_TYPES.UnaryExpression &&
expression.operator === '!'
) {
return checkNode(expression.argument, !isUnaryNotArgument, node);
}
// Since typescript array index signature types don't represent the
// possibility of out-of-bounds access, if we're indexing into an array
// just skip the check, to avoid false positives
if (!isNoUncheckedIndexedAccess && isArrayIndexExpression(expression)) {
return;
}
// When checking logical expressions, only check the right side
// as the left side has been checked by checkLogicalExpressionForUnnecessaryConditionals
//
// Unless the node is nullish coalescing, as it's common to use patterns like `nullBool ?? true` to to strict
// boolean checks if we inspect the right here, it'll usually be a constant condition on purpose.
// In this case it's better to inspect the type of the expression as a whole.
if (
expression.type === AST_NODE_TYPES.LogicalExpression &&
expression.operator !== '??'
) {
return checkNode(expression.right);
}
const type = getConstrainedTypeAtLocation(services, expression);
if (isConditionalAlwaysNecessary(type)) {
return;
}
let messageId: MessageId | null = null;
if (isTypeFlagSet(type, ts.TypeFlags.Never)) {
messageId = 'never';
} else if (!isPossiblyTruthy(type)) {
messageId = !isUnaryNotArgument ? 'alwaysFalsy' : 'alwaysTruthy';
} else if (!isPossiblyFalsy(type)) {
messageId = !isUnaryNotArgument ? 'alwaysTruthy' : 'alwaysFalsy';
}
if (messageId) {
context.report({ node, messageId });
}
}
-
JSDoc:
-
Parameters:
expression: TSESTree.Expression
isUnaryNotArgument: boolean
node: TSESTree.Expression
- Return Type:
void
- Calls:
checkNode
isArrayIndexExpression
getConstrainedTypeAtLocation (from ../util)
isConditionalAlwaysNecessary
isTypeFlagSet (from ../util)
isPossiblyTruthy (from ../util)
isPossiblyFalsy (from ../util)
context.report
- Internal Comments:
// Check if the node is Unary Negation expression and handle it // Since typescript array index signature types don't represent the // possibility of out-of-bounds access, if we're indexing into an array // just skip the check, to avoid false positives // When checking logical expressions, only check the right side // as the left side has been checked by checkLogicalExpressionForUnnecessaryConditionals // // Unless the node is nullish coalescing, as it's common to use patterns like `nullBool ?? true` to to strict // boolean checks if we inspect the right here, it'll usually be a constant condition on purpose. // In this case it's better to inspect the type of the expression as a whole.
checkNodeForNullish(node: TSESTree.Expression): void
¶
Code
function checkNodeForNullish(node: TSESTree.Expression): void {
const type = getConstrainedTypeAtLocation(services, node);
// Conditional is always necessary if it involves `any`, `unknown` or a naked type parameter
if (
isTypeFlagSet(
type,
ts.TypeFlags.Any |
ts.TypeFlags.Unknown |
ts.TypeFlags.TypeParameter |
ts.TypeFlags.TypeVariable,
)
) {
return;
}
let messageId: MessageId | null = null;
if (isTypeFlagSet(type, ts.TypeFlags.Never)) {
messageId = 'never';
} else if (
!isPossiblyNullish(type) &&
!(
node.type === AST_NODE_TYPES.MemberExpression &&
isNullableMemberExpression(node)
)
) {
// Since typescript array index signature types don't represent the
// possibility of out-of-bounds access, if we're indexing into an array
// just skip the check, to avoid false positives
if (
isNoUncheckedIndexedAccess ||
(!isArrayIndexExpression(node) &&
!(
node.type === AST_NODE_TYPES.ChainExpression &&
node.expression.type !== AST_NODE_TYPES.TSNonNullExpression &&
optionChainContainsOptionArrayIndex(node.expression)
))
) {
messageId = 'neverNullish';
}
} else if (isAlwaysNullish(type)) {
messageId = 'alwaysNullish';
}
if (messageId) {
context.report({ node, messageId });
}
}
- Parameters:
node: TSESTree.Expression
- Return Type:
void
- Calls:
getConstrainedTypeAtLocation (from ../util)
isTypeFlagSet (from ../util)
isPossiblyNullish
isNullableMemberExpression
isArrayIndexExpression
optionChainContainsOptionArrayIndex
isAlwaysNullish
context.report
- Internal Comments:
checkIfBoolExpressionIsNecessaryConditional(node: TSESTree.Node, left: TSESTree.Node, right: TSESTree.Node, operator: BoolOperator): void
¶
Code
function checkIfBoolExpressionIsNecessaryConditional(
node: TSESTree.Node,
left: TSESTree.Node,
right: TSESTree.Node,
operator: BoolOperator,
): void {
const leftType = getConstrainedTypeAtLocation(services, left);
const rightType = getConstrainedTypeAtLocation(services, right);
const leftStaticValue = toStaticValue(leftType);
const rightStaticValue = toStaticValue(rightType);
if (leftStaticValue != null && rightStaticValue != null) {
const conditionIsTrue = booleanComparison(
leftStaticValue.value,
operator,
rightStaticValue.value,
);
context.report({
node,
messageId: 'comparisonBetweenLiteralTypes',
data: {
left: checker.typeToString(leftType),
operator,
right: checker.typeToString(rightType),
trueOrFalse: conditionIsTrue ? 'true' : 'false',
},
});
return;
}
// Workaround for https://github.com/microsoft/TypeScript/issues/37160
if (isStrictNullChecks) {
const UNDEFINED = ts.TypeFlags.Undefined;
const NULL = ts.TypeFlags.Null;
const VOID = ts.TypeFlags.Void;
const isComparable = (type: ts.Type, flag: ts.TypeFlags): boolean => {
// Allow comparison to `any`, `unknown` or a naked type parameter.
flag |=
ts.TypeFlags.Any |
ts.TypeFlags.Unknown |
ts.TypeFlags.TypeParameter |
ts.TypeFlags.TypeVariable;
// Allow loose comparison to nullish values.
if (operator === '==' || operator === '!=') {
flag |= NULL | UNDEFINED | VOID;
}
return isTypeFlagSet(type, flag);
};
if (
(leftType.flags === UNDEFINED &&
!isComparable(rightType, UNDEFINED | VOID)) ||
(rightType.flags === UNDEFINED &&
!isComparable(leftType, UNDEFINED | VOID)) ||
(leftType.flags === NULL && !isComparable(rightType, NULL)) ||
(rightType.flags === NULL && !isComparable(leftType, NULL))
) {
context.report({ node, messageId: 'noOverlapBooleanExpression' });
return;
}
}
}
-
JSDoc:
/** * Checks that a binary expression is necessarily conditional, reports otherwise. * If both sides of the binary expression are literal values, it's not a necessary condition. * * NOTE: It's also unnecessary if the types that don't overlap at all * but that case is handled by the Typescript compiler itself. * Known exceptions: * - https://github.com/microsoft/TypeScript/issues/32627 * - https://github.com/microsoft/TypeScript/issues/37160 (handled) */
-
Parameters:
node: TSESTree.Node
left: TSESTree.Node
right: TSESTree.Node
operator: BoolOperator
- Return Type:
void
- Calls:
getConstrainedTypeAtLocation (from ../util)
toStaticValue
booleanComparison
context.report
checker.typeToString
isTypeFlagSet (from ../util)
isComparable
- Internal Comments:
isComparable(type: ts.Type, flag: ts.TypeFlags): boolean
¶
Code
(type: ts.Type, flag: ts.TypeFlags): boolean => {
// Allow comparison to `any`, `unknown` or a naked type parameter.
flag |=
ts.TypeFlags.Any |
ts.TypeFlags.Unknown |
ts.TypeFlags.TypeParameter |
ts.TypeFlags.TypeVariable;
// Allow loose comparison to nullish values.
if (operator === '==' || operator === '!=') {
flag |= NULL | UNDEFINED | VOID;
}
return isTypeFlagSet(type, flag);
}
- Parameters:
type: ts.Type
flag: ts.TypeFlags
- Return Type:
boolean
- Calls:
isTypeFlagSet (from ../util)
- Internal Comments:
checkLogicalExpressionForUnnecessaryConditionals(node: TSESTree.LogicalExpression): void
¶
Code
function checkLogicalExpressionForUnnecessaryConditionals(
node: TSESTree.LogicalExpression,
): void {
if (node.operator === '??') {
checkNodeForNullish(node.left);
return;
}
// Only checks the left side, since the right side might not be "conditional" at all.
// The right side will be checked if the LogicalExpression is used in a conditional context
checkNode(node.left);
}
-
JSDoc:
-
Parameters:
node: TSESTree.LogicalExpression
- Return Type:
void
- Calls:
checkNodeForNullish
checkNode
- Internal Comments:
checkIfWhileLoopIsNecessaryConditional(node: TSESTree.WhileStatement): void
¶
Code
function checkIfWhileLoopIsNecessaryConditional(
node: TSESTree.WhileStatement,
): void {
if (
allowConstantLoopConditionsOption === 'only-allowed-literals' &&
node.test.type === AST_NODE_TYPES.Literal &&
constantLoopConditionsAllowedLiterals.has(node.test.value)
) {
return;
}
checkIfLoopIsNecessaryConditional(node);
}
- Parameters:
node: TSESTree.WhileStatement
- Return Type:
void
- Calls:
constantLoopConditionsAllowedLiterals.has
checkIfLoopIsNecessaryConditional
`checkIfLoopIsNecessaryConditional(node: | TSESTree.DoWhileStatement¶
| TSESTree.ForStatement
| TSESTree.WhileStatement): void`
Code
function checkIfLoopIsNecessaryConditional(
node:
| TSESTree.DoWhileStatement
| TSESTree.ForStatement
| TSESTree.WhileStatement,
): void {
if (node.test == null) {
// e.g. `for(;;)`
return;
}
if (
allowConstantLoopConditionsOption === 'always' &&
tsutils.isTrueLiteralType(
getConstrainedTypeAtLocation(services, node.test),
)
) {
return;
}
checkNode(node.test);
}
-
JSDoc:
-
Parameters:
node: | TSESTree.DoWhileStatement | TSESTree.ForStatement | TSESTree.WhileStatement
- Return Type:
void
- Calls:
tsutils.isTrueLiteralType
getConstrainedTypeAtLocation (from ../util)
checkNode
- Internal Comments:
checkCallExpression(node: TSESTree.CallExpression): void
¶
Code
function checkCallExpression(node: TSESTree.CallExpression): void {
if (checkTypePredicates) {
const truthinessAssertedArgument = findTruthinessAssertedArgument(
services,
node,
);
if (truthinessAssertedArgument != null) {
checkNode(truthinessAssertedArgument);
}
const typeGuardAssertedArgument = findTypeGuardAssertedArgument(
services,
node,
);
if (typeGuardAssertedArgument != null) {
const typeOfArgument = getConstrainedTypeAtLocation(
services,
typeGuardAssertedArgument.argument,
);
if (typeOfArgument === typeGuardAssertedArgument.type) {
context.report({
node: typeGuardAssertedArgument.argument,
messageId: 'typeGuardAlreadyIsType',
data: {
typeGuardOrAssertionFunction: typeGuardAssertedArgument.asserts
? 'assertion function'
: 'type guard',
},
});
}
}
}
// If this is something like arr.filter(x => /*condition*/), check `condition`
if (
isArrayMethodCallWithPredicate(context, services, node) &&
node.arguments.length
) {
const callback = node.arguments[0];
// Inline defined functions
if (
callback.type === AST_NODE_TYPES.ArrowFunctionExpression ||
callback.type === AST_NODE_TYPES.FunctionExpression
) {
// Two special cases, where we can directly check the node that's returned:
// () => something
if (callback.body.type !== AST_NODE_TYPES.BlockStatement) {
return checkNode(callback.body);
}
// () => { return something; }
const callbackBody = callback.body.body;
if (
callbackBody.length === 1 &&
callbackBody[0].type === AST_NODE_TYPES.ReturnStatement &&
callbackBody[0].argument
) {
return checkNode(callbackBody[0].argument);
}
// Potential enhancement: could use code-path analysis to check
// any function with a single return statement
// (Value to complexity ratio is dubious however)
}
// Otherwise just do type analysis on the function as a whole.
const returnTypes = tsutils
.getCallSignaturesOfType(
getConstrainedTypeAtLocation(services, callback),
)
.map(sig => sig.getReturnType());
if (returnTypes.length === 0) {
// Not a callable function, e.g. `any`
return;
}
let hasFalsyReturnTypes = false;
let hasTruthyReturnTypes = false;
for (const type of returnTypes) {
const { constraintType } = getConstraintInfo(checker, type);
// Predicate is always necessary if it involves `any` or `unknown`
if (
!constraintType ||
isTypeAnyType(constraintType) ||
isTypeUnknownType(constraintType)
) {
return;
}
if (isPossiblyFalsy(constraintType)) {
hasFalsyReturnTypes = true;
}
if (isPossiblyTruthy(constraintType)) {
hasTruthyReturnTypes = true;
}
// bail early if both a possibly-truthy and a possibly-falsy have been detected
if (hasFalsyReturnTypes && hasTruthyReturnTypes) {
return;
}
}
if (!hasFalsyReturnTypes) {
return context.report({
node: callback,
messageId: 'alwaysTruthyFunc',
});
}
if (!hasTruthyReturnTypes) {
return context.report({
node: callback,
messageId: 'alwaysFalsyFunc',
});
}
}
}
- Parameters:
node: TSESTree.CallExpression
- Return Type:
void
- Calls:
findTruthinessAssertedArgument (from ../util/assertionFunctionUtils)
checkNode
findTypeGuardAssertedArgument (from ../util/assertionFunctionUtils)
getConstrainedTypeAtLocation (from ../util)
context.report
isArrayMethodCallWithPredicate (from ../util)
tsutils .getCallSignaturesOfType( getConstrainedTypeAtLocation(services, callback), ) .map
sig.getReturnType
getConstraintInfo (from ../util)
isTypeAnyType (from ../util)
isTypeUnknownType (from ../util)
isPossiblyFalsy (from ../util)
isPossiblyTruthy (from ../util)
- Internal Comments:
// If this is something like arr.filter(x => /*condition*/), check `condition` // Inline defined functions // Two special cases, where we can directly check the node that's returned: // () => something // () => { return something; } (x2) // Otherwise just do type analysis on the function as a whole. (x2) // Not a callable function, e.g. `any` // Predicate is always necessary if it involves `any` or `unknown` // bail early if both a possibly-truthy and a possibly-falsy have been detected
optionChainContainsOptionArrayIndex(node: TSESTree.CallExpression | TSESTree.MemberExpression): boolean
¶
Code
function optionChainContainsOptionArrayIndex(
node: TSESTree.CallExpression | TSESTree.MemberExpression,
): boolean {
const lhsNode =
node.type === AST_NODE_TYPES.CallExpression ? node.callee : node.object;
if (node.optional && isArrayIndexExpression(lhsNode)) {
return true;
}
if (
lhsNode.type === AST_NODE_TYPES.MemberExpression ||
lhsNode.type === AST_NODE_TYPES.CallExpression
) {
return optionChainContainsOptionArrayIndex(lhsNode);
}
return false;
}
- Parameters:
node: TSESTree.CallExpression | TSESTree.MemberExpression
- Return Type:
boolean
- Calls:
isArrayIndexExpression
optionChainContainsOptionArrayIndex
isNullablePropertyType(objType: ts.Type, propertyType: ts.Type): boolean
¶
Code
function isNullablePropertyType(
objType: ts.Type,
propertyType: ts.Type,
): boolean {
if (propertyType.isUnion()) {
return propertyType.types.some(type =>
isNullablePropertyType(objType, type),
);
}
if (propertyType.isNumberLiteral() || propertyType.isStringLiteral()) {
const propType = getTypeOfPropertyOfName(
checker,
objType,
propertyType.value.toString(),
);
if (propType) {
return isNullableType(propType);
}
}
const typeName = getTypeName(checker, propertyType);
return checker
.getIndexInfosOfType(objType)
.some(info => getTypeName(checker, info.keyType) === typeName);
}
- Parameters:
objType: ts.Type
propertyType: ts.Type
- Return Type:
boolean
- Calls:
propertyType.isUnion
propertyType.types.some
isNullablePropertyType
propertyType.isNumberLiteral
propertyType.isStringLiteral
getTypeOfPropertyOfName (from ../util)
propertyType.value.toString
isNullableType (from ../util)
getTypeName (from ../util)
checker .getIndexInfosOfType(objType) .some
isMemberExpressionNullableOriginFromObject(node: TSESTree.MemberExpression): boolean
¶
Code
function isMemberExpressionNullableOriginFromObject(
node: TSESTree.MemberExpression,
): boolean {
const prevType = getConstrainedTypeAtLocation(services, node.object);
const property = node.property;
if (prevType.isUnion() && isIdentifier(property)) {
const isOwnNullable = prevType.types.some(type => {
if (node.computed) {
const propertyType = getConstrainedTypeAtLocation(
services,
node.property,
);
return isNullablePropertyType(type, propertyType);
}
const propType = getTypeOfPropertyOfName(
checker,
type,
property.name,
);
if (propType) {
return isNullableType(propType);
}
const indexInfo = checker.getIndexInfosOfType(type);
return indexInfo.some(info => {
const isStringTypeName =
getTypeName(checker, info.keyType) === 'string';
return (
isStringTypeName &&
(isNoUncheckedIndexedAccess || isNullableType(info.type))
);
});
});
return !isOwnNullable && isNullableType(prevType);
}
return false;
}
- Parameters:
node: TSESTree.MemberExpression
- Return Type:
boolean
- Calls:
getConstrainedTypeAtLocation (from ../util)
prevType.isUnion
isIdentifier (from ../util)
prevType.types.some
isNullablePropertyType
getTypeOfPropertyOfName (from ../util)
isNullableType (from ../util)
checker.getIndexInfosOfType
indexInfo.some
getTypeName (from ../util)
isCallExpressionNullableOriginFromCallee(node: TSESTree.CallExpression): boolean
¶
Code
function isCallExpressionNullableOriginFromCallee(
node: TSESTree.CallExpression,
): boolean {
const prevType = getConstrainedTypeAtLocation(services, node.callee);
if (prevType.isUnion()) {
const isOwnNullable = prevType.types.some(type => {
const signatures = type.getCallSignatures();
return signatures.some(sig => isNullableType(sig.getReturnType()));
});
return !isOwnNullable && isNullableType(prevType);
}
return false;
}
- Parameters:
node: TSESTree.CallExpression
- Return Type:
boolean
- Calls:
getConstrainedTypeAtLocation (from ../util)
prevType.isUnion
prevType.types.some
type.getCallSignatures
signatures.some
isNullableType (from ../util)
sig.getReturnType
isOptionableExpression(node: TSESTree.Expression): boolean
¶
Code
function isOptionableExpression(node: TSESTree.Expression): boolean {
const type = getConstrainedTypeAtLocation(services, node);
const isOwnNullable =
node.type === AST_NODE_TYPES.MemberExpression
? !isMemberExpressionNullableOriginFromObject(node)
: node.type === AST_NODE_TYPES.CallExpression
? !isCallExpressionNullableOriginFromCallee(node)
: true;
return (
isConditionalAlwaysNecessary(type) ||
(isOwnNullable && isNullableType(type))
);
}
- Parameters:
node: TSESTree.Expression
- Return Type:
boolean
- Calls:
getConstrainedTypeAtLocation (from ../util)
isMemberExpressionNullableOriginFromObject
isCallExpressionNullableOriginFromCallee
isConditionalAlwaysNecessary
isNullableType (from ../util)
checkOptionalChain(node: TSESTree.CallExpression | TSESTree.MemberExpression, beforeOperator: TSESTree.Node, fix: '' | '.'): void
¶
Code
function checkOptionalChain(
node: TSESTree.CallExpression | TSESTree.MemberExpression,
beforeOperator: TSESTree.Node,
fix: '' | '.',
): void {
// We only care if this step in the chain is optional. If just descend
// from an optional chain, then that's fine.
if (!node.optional) {
return;
}
// Since typescript array index signature types don't represent the
// possibility of out-of-bounds access, if we're indexing into an array
// just skip the check, to avoid false positives
if (
!isNoUncheckedIndexedAccess &&
optionChainContainsOptionArrayIndex(node)
) {
return;
}
const nodeToCheck =
node.type === AST_NODE_TYPES.CallExpression ? node.callee : node.object;
if (isOptionableExpression(nodeToCheck)) {
return;
}
const questionDotOperator = nullThrows(
context.sourceCode.getTokenAfter(
beforeOperator,
token =>
token.type === AST_TOKEN_TYPES.Punctuator && token.value === '?.',
),
NullThrowsReasons.MissingToken('operator', node.type),
);
context.report({
loc: questionDotOperator.loc,
node,
messageId: 'neverOptionalChain',
suggest: [
{
messageId: 'suggestRemoveOptionalChain',
fix(fixer) {
return fixer.replaceText(questionDotOperator, fix);
},
},
],
});
}
- Parameters:
node: TSESTree.CallExpression | TSESTree.MemberExpression
beforeOperator: TSESTree.Node
fix: '' | '.'
- Return Type:
void
- Calls:
optionChainContainsOptionArrayIndex
isOptionableExpression
nullThrows (from ../util)
context.sourceCode.getTokenAfter
NullThrowsReasons.MissingToken
context.report
fixer.replaceText
- Internal Comments:
// We only care if this step in the chain is optional. If just descend // from an optional chain, then that's fine. // Since typescript array index signature types don't represent the // possibility of out-of-bounds access, if we're indexing into an array // just skip the check, to avoid false positives
checkOptionalMemberExpression(node: TSESTree.MemberExpression): void
¶
Code
- Parameters:
node: TSESTree.MemberExpression
- Return Type:
void
- Calls:
checkOptionalChain
checkOptionalCallExpression(node: TSESTree.CallExpression): void
¶
Code
- Parameters:
node: TSESTree.CallExpression
- Return Type:
void
- Calls:
checkOptionalChain
checkAssignmentExpression(node: TSESTree.AssignmentExpression): void
¶
Code
function checkAssignmentExpression(
node: TSESTree.AssignmentExpression,
): void {
// Similar to checkLogicalExpressionForUnnecessaryConditionals, since
// a ||= b is equivalent to a || (a = b)
if (['&&=', '||='].includes(node.operator)) {
checkNode(node.left);
} else if (node.operator === '??=') {
checkNodeForNullish(node.left);
}
}
- Parameters:
node: TSESTree.AssignmentExpression
- Return Type:
void
- Calls:
['&&=', '||='].includes
checkNode
checkNodeForNullish
- Internal Comments:
ConditionalExpression(node: any): void
¶
- Parameters:
node: any
- Return Type:
void
- Calls:
checkNode
IfStatement(node: any): void
¶
- Parameters:
node: any
- Return Type:
void
- Calls:
checkNode
ConditionalExpression(node: any): void
¶
- Parameters:
node: any
- Return Type:
void
- Calls:
checkNode
IfStatement(node: any): void
¶
- Parameters:
node: any
- Return Type:
void
- Calls:
checkNode
ConditionalExpression(node: any): void
¶
- Parameters:
node: any
- Return Type:
void
- Calls:
checkNode
IfStatement(node: any): void
¶
- Parameters:
node: any
- Return Type:
void
- Calls:
checkNode
ConditionalExpression(node: any): void
¶
- Parameters:
node: any
- Return Type:
void
- Calls:
checkNode
IfStatement(node: any): void
¶
- Parameters:
node: any
- Return Type:
void
- Calls:
checkNode
`normalizeAllowConstantLoopConditions(allowConstantLoopConditions: | AllowConstantLoopConditions¶
| LegacyAllowConstantLoopConditions): AllowConstantLoopConditions`
Code
function normalizeAllowConstantLoopConditions(
allowConstantLoopConditions:
| AllowConstantLoopConditions
| LegacyAllowConstantLoopConditions,
): AllowConstantLoopConditions {
if (allowConstantLoopConditions === true) {
return 'always';
}
if (allowConstantLoopConditions === false) {
return 'never';
}
return allowConstantLoopConditions;
}
- Parameters:
allowConstantLoopConditions: | AllowConstantLoopConditions | LegacyAllowConstantLoopConditions
- Return Type:
AllowConstantLoopConditions
Type Aliases¶
BoolOperator
¶
LegacyAllowConstantLoopConditions
¶
AllowConstantLoopConditions
¶
Options
¶
type Options = [
{
allowConstantLoopConditions?:
| AllowConstantLoopConditions
| LegacyAllowConstantLoopConditions;
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing?: boolean;
checkTypePredicates?: boolean;
},
];
MessageId
¶
type MessageId = | 'alwaysFalsy'
| 'alwaysFalsyFunc'
| 'alwaysNullish'
| 'alwaysTruthy'
| 'alwaysTruthyFunc'
| 'comparisonBetweenLiteralTypes'
| 'never'
| 'neverNullish'
| 'neverOptionalChain'
| 'noOverlapBooleanExpression'
| 'noStrictNullCheck'
| 'suggestRemoveOptionalChain'
| 'typeGuardAlreadyIsType';