β¬ οΈ Back to Table of Contents
π no-unnecessary-type-assertion¶
π Analysis Summary¶
| Metric | Count |
|---|---|
| π§ Functions | 38 |
| π¦ Imports | 18 |
| π Type Aliases | 2 |
π Table of Contents¶
π οΈ File Location:¶
π packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts
π€ Default Export¶
| Property | Value |
|---|---|
name |
'no-unnecessary-type-assertion' |
meta.type |
'suggestion' |
meta.docs.description |
'Disallow type assertions that do not change the type of an expression' |
meta.docs.recommended |
'recommended' |
meta.docs.requiresTypeChecking |
true |
meta.fixable |
'code' |
meta.messages.contextuallyUnnecessary |
'This assertion is unnecessary since the receiver accepts the original type of the expression.' |
meta.messages.unnecessaryAssertion |
'This assertion is unnecessary since it does not change the type of the expression.' |
meta.schema |
[ { type: 'object', additionalProperties: false, properties: { checkLiteralConstAssertions: { type: 'boolean', descri... |
defaultOptions |
[{}] |
Entry point: create β documented under Functions.
π¦ Imports¶
| Name | Source |
|---|---|
Scope |
@typescript-eslint/scope-manager |
TSESTree |
@typescript-eslint/utils |
ReportFixFunction |
@typescript-eslint/utils/ts-eslint |
RuleFix |
@typescript-eslint/utils/ts-eslint |
AST_NODE_TYPES |
@typescript-eslint/utils |
AST_TOKEN_TYPES |
@typescript-eslint/utils |
createRule |
../util |
getConstrainedTypeAtLocation |
../util |
getContextualType |
../util |
getDeclaration |
../util |
getModifiers |
../util |
getParserServices |
../util |
isNullableType |
../util |
isStartOfArrowFunctionBody |
../util |
isStartOfExpressionStatement |
../util |
isTypeFlagSet |
../util |
nullThrows |
../util |
NullThrowsReasons |
../util |
Functions¶
create(context: any, [options]: any): { 'TSAsExpression, TSTypeAssertion'(node: TSESTree.TSAsExpr⦶
Parameters:
contextany[options]any
Returns: { 'TSAsExpression, TSTypeAssertion'(node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion): void; TSNonNullExpression(node: any): void; }
Calls:
getParserServices (from ../util)services.program.getTypeCheckerservices.program.getCompilerOptionsgetDeclaration (from ../util)tsutils.isStrictCompilerOptionEnabledts.isVariableDeclarationts.isVariableDeclarationListservices.tsNodeToESTreeNodeMap.hasservices.tsNodeToESTreeNodeMap.getcontext.sourceCode.getScopechecker.getTypeFromTypeNodegetConstrainedTypeAtLocation (from ../util)ts.isVariableStatementtsutils.includesModifiergetModifiers (from ../util)isTemplateLiteralWithExpressionscontainsTypeVariableisTypeFlagSet (from ../util)tsutils.isCompilerOptionEnabledareUnionPartsEquivalentIgnoringUndefinedhasIndexSignaturecontainsAnyisConceptuallyLiteralcast .getProperties() .someisTypeLiteralchecker.getTypeOfSymbolcast.isIntersectionuncast.isIntersectioncastParts.findtsutils.isTypeParametercastParts.someisEmptyObjectTypechecker.getBaseConstraintOfTypeisNullableType (from ../util)hasSamePropertieshaveSameTypeArgumentsareMutuallyAssignabletype.isLiteraltsutils.isBooleanLiteralTypetsutils .unionConstituents(type) .somechecker.getIndexInfosOfTypetsutils.isTypeReferencechecker.getTypeArgumentsseen.hasseen.addpredicatetype.isUnionOrIntersectiontype.types.sometypeContainsgetTypeArgumentstype .getCallSignatures() .flatMapsig.getReturnTypesig.getParameters().mapnestedTypes.somesig.getTypeParameterscontextual.getProperties().somechecker.getSignaturesOfTypecontextualSigs.someuncast.getPropertyprop.getEscapedNamechecker .getSignaturesOfType( checker.getTypeOfSymbol(uncastProp), ts.SignatureKind.Call, ) .someuncast.getPropertiescast.getPropertiescastProps.mapp.getEscapedNameuncastProps.everycastPropNames.hastsutils.isPropertyReadonlyInTypeuncastArgs.everychecker.isTypeAssignableTotsutils .unionConstituents(uncast) .filtertsutils.unionConstituents(cast).filtercastParts.everyuncastPartsSet.hasgetOriginalExpressionservices.getTypeAtLocationisTypeUnchangedCONCEPTUALLY_LITERAL_TYPES.hastype.getPropertiestype.getCallSignaturestype.getConstructSignaturestype.getStringIndexTypetype.getNumberIndexTypetype.getCallSignatures().someparent.arguments.includesservices .getTypeAtLocation(parent.callee) .getNonNullableTypecalleeType.getCallSignaturesparent.arguments.indexOfsignatures.mapsig.getParametersts.isParameterparamTypes.somedefinedParamTypes.everyservices.esTreeNodeToTSNodeMap.getchecker.getContextualType(objectTsNode)?.isUnionchecker.getContextualTypechecker.getNonNullableTypenonNullableContextualType.isUnioncurrent.arguments.includeschecker.getTypeAtLocationhasGenericCallSignatureisInGenericContexthasPhantomTypeArgumentsSKIP_PARENT_TYPES.hasisInDestructuringDeclarationisPropertyInProblematicContextisAssignmentInNonStatementContextisRightHandSideOfLogicalAssignmentisArgumentToOverloadedFunctionisIIFEfunctionType.getCallSignatureschecker.getReturnTypeOfSignaturechecker.getVoidTypenullThrows (from ../util)context.sourceCode.getTokenBeforeNullThrowsReasons.MissingTokencontext.sourceCode.getTokenAfter['{', 'function', 'class'].includesisStartOfExpressionStatement (from ../util)isStartOfArrowFunctionBody (from ../util)fixes.pushfixer.insertTextBeforefixer.removeRangefixer.insertTextAfterisDoubleAssertionUnnecessarycontext.reportcontext.sourceCode.getTextfixer.replaceTextoptions.typesToIgnore?.includesisConstAssertiongetUncastTypeisImplicitlyNarrowedLiteralDeclarationcreateAssertionFixershouldSkipContextualTypeFallbacknode.parent.arguments.includescastType.isUnionhasPhantomTypeArgumentMismatchgenericsMismatchreportDoubleAssertionIfUnnecessarycontext.sourceCode.getLastTokenisPossiblyUsedBeforeAssignedgetContextualType (from ../util)
Internal Comments:
/**
* Returns true if there's a chance the variable has been used before a value has been assigned to it
*/
// don't know what the declaration is for some reason, so just assume the worst
// non-strict mode doesn't care about used before assigned errors (x4)
// ignore class properties as they are compile time guarded (x3)
// also ignore function arguments as they can't be used before defined (x3)
// For var declarations, we need to check whether the node
// is actually in a descendant of its declaration or not. If not,
// it may be used before defined.
// eg
// if (Math.random() < 0.5) {
// var x: number = 2;
// } else {
// x!.toFixed();
// }
// var (x4)
// If they are not in the same file it will not exist. (x4)
// This situation must not occur using before defined. (x4)
// is it `const x!: number` (x5)
// check if the defined variable type has changed since assignment (x2)
// `declare`s are never narrowed, so never skip them
// possibly used before assigned, so just skip it
// better to false negative and skip it, than false positive and fix to compile erroring code
//
// no better way to figure this out right now
// https://github.com/Microsoft/TypeScript/issues/31124
/**
* Even on `const` variable declarations, template literals with expressions can sometimes be widened without a type assertion.
* @see https://github.com/typescript-eslint/typescript-eslint/issues/8737
*/
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion (x2)
// Workaround for https://github.com/typescript-eslint/typescript-eslint/issues/12705
// An optional-chained callee (`foo?.bar(...)`) types as `<method> | undefined`, (x2)
// and a union exposes no call signatures β strip the nullability first. (x2)
/**
* Interpolated template literals can be widened to `string` while contextual
* typing still accepts them, so the assertion may be required.
* @see https://github.com/typescript-eslint/typescript-eslint/issues/12276
*/
// Special handling for IIFE: extract the function's return type
// If the function has no explicit return type annotation and returns undefined,
// treat it as void (TypeScript infers () => {} as () => undefined, but it should be void)
// Removing the angle brackets leaves the asserted operand at the (x2)
// assertion's position, so its first token leads whatever the (x2)
// assertion led. A leading `{`/`function`/`class` at the start of an (x2)
// expression statement is parsed as a block / function or class (x2)
// declaration, and a leading `{` at the start of a concise arrow body (x2)
// is parsed as a block body. In those positions the operand must be (x2)
// wrapped in parentheses to stay an expression. (x2)
// for all other = assignments we ignore non-null checks
// this is because non-null assertions can change the type-flow of the code
// so whilst they might be unnecessary for the assignment - they are necessary
// for following code
// Check both the constrained type and the actual type. (x2)
// If either is nullable, we should not report the assertion as unnecessary. (x2)
// This handles cases like generic constraints with `any` where the (x2)
// constrained type is `any` (nullable) but the actual type might be (x2)
// a type parameter that TypeScript treats nominally. (x2)
// See: https://github.com/typescript-eslint/typescript-eslint/issues/11559 (x3)
// we know it's a nullable type
// so figure out if the variable is used in a place that accepts nullable types
// If the constrained type differs from the actual type (e.g., when dealing
// with unresolved generic type parameters), we should not report the assertion
// as contextually unnecessary. TypeScript may still require the assertion
// even if the constraint is nullable (like `any`).
// in strict mode you can't assign null to undefined, so we have to make sure that (x2)
// the two types share a nullable type (x2)
// make sure that the parent accepts the same types (x2)
// i.e. assigning `string | null | undefined` to `string | undefined` is invalid (x2)
Code
create(context, [options]) {
const services = getParserServices(context);
const checker = services.program.getTypeChecker();
const compilerOptions = services.program.getCompilerOptions();
/**
* Returns true if there's a chance the variable has been used before a value has been assigned to it
*/
function isPossiblyUsedBeforeAssigned(node: TSESTree.Expression): boolean {
const declaration = getDeclaration(services, node);
if (!declaration) {
// don't know what the declaration is for some reason, so just assume the worst
return true;
}
if (
// non-strict mode doesn't care about used before assigned errors
tsutils.isStrictCompilerOptionEnabled(
compilerOptions,
'strictNullChecks',
) &&
// ignore class properties as they are compile time guarded
// also ignore function arguments as they can't be used before defined
ts.isVariableDeclaration(declaration)
) {
// For var declarations, we need to check whether the node
// is actually in a descendant of its declaration or not. If not,
// it may be used before defined.
// eg
// if (Math.random() < 0.5) {
// var x: number = 2;
// } else {
// x!.toFixed();
// }
if (
ts.isVariableDeclarationList(declaration.parent) &&
// var
declaration.parent.flags === ts.NodeFlags.None &&
// If they are not in the same file it will not exist.
// This situation must not occur using before defined.
services.tsNodeToESTreeNodeMap.has(declaration)
) {
const declaratorNode: TSESTree.VariableDeclaration =
services.tsNodeToESTreeNodeMap.get(declaration);
const scope = context.sourceCode.getScope(node);
const declaratorScope = context.sourceCode.getScope(declaratorNode);
let parentScope: Scope | null = declaratorScope;
while ((parentScope = parentScope.upper)) {
if (parentScope === scope) {
return true;
}
}
}
if (
// is it `const x!: number`
declaration.initializer == null &&
declaration.exclamationToken == null &&
declaration.type != null
) {
// check if the defined variable type has changed since assignment
const declarationType = checker.getTypeFromTypeNode(declaration.type);
const type = getConstrainedTypeAtLocation(services, node);
if (
declarationType === type &&
// `declare`s are never narrowed, so never skip them
!(
ts.isVariableDeclarationList(declaration.parent) &&
ts.isVariableStatement(declaration.parent.parent) &&
tsutils.includesModifier(
getModifiers(declaration.parent.parent),
ts.SyntaxKind.DeclareKeyword,
)
)
) {
// possibly used before assigned, so just skip it
// better to false negative and skip it, than false positive and fix to compile erroring code
//
// no better way to figure this out right now
// https://github.com/Microsoft/TypeScript/issues/31124
return true;
}
}
}
return false;
}
function isConstAssertion(node: TSESTree.TypeNode): boolean {
return (
node.type === AST_NODE_TYPES.TSTypeReference &&
node.typeName.type === AST_NODE_TYPES.Identifier &&
node.typeName.name === 'const'
);
}
function isTemplateLiteralWithExpressions(expression: TSESTree.Expression) {
return (
expression.type === AST_NODE_TYPES.TemplateLiteral &&
expression.expressions.length !== 0
);
}
function isImplicitlyNarrowedLiteralDeclaration({
expression,
parent,
}: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion): boolean {
/**
* Even on `const` variable declarations, template literals with expressions can sometimes be widened without a type assertion.
* @see https://github.com/typescript-eslint/typescript-eslint/issues/8737
*/
if (isTemplateLiteralWithExpressions(expression)) {
return false;
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const maybeDeclarationNode = parent.parent!;
return (
(maybeDeclarationNode.type === AST_NODE_TYPES.VariableDeclaration &&
maybeDeclarationNode.kind === 'const') ||
(parent.type === AST_NODE_TYPES.PropertyDefinition && parent.readonly)
);
}
function isTypeUnchanged(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
expression: TSESTree.Expression,
uncast: ts.Type,
cast: ts.Type,
): boolean {
if (uncast === cast) {
return true;
}
if (
node.typeAnnotation.type === AST_NODE_TYPES.TSIntersectionType &&
containsTypeVariable(cast)
) {
return false;
}
if (
isTypeFlagSet(uncast, ts.TypeFlags.Undefined) &&
isTypeFlagSet(cast, ts.TypeFlags.Undefined) &&
tsutils.isCompilerOptionEnabled(
compilerOptions,
'exactOptionalPropertyTypes',
)
) {
return areUnionPartsEquivalentIgnoringUndefined(uncast, cast);
}
if (
(isTypeFlagSet(uncast, ts.TypeFlags.NonPrimitive) &&
!isTypeFlagSet(cast, ts.TypeFlags.NonPrimitive)) ||
(hasIndexSignature(uncast) && !hasIndexSignature(cast)) ||
containsAny(uncast) ||
containsAny(cast) ||
(containsTypeVariable(cast) && !containsTypeVariable(uncast))
) {
return false;
}
if (
isConceptuallyLiteral(expression) &&
(expression.type !== AST_NODE_TYPES.ObjectExpression ||
expression.properties.length === 0 ||
cast
.getProperties()
.some(p => isTypeLiteral(checker.getTypeOfSymbol(p))))
) {
return false;
}
if (cast.isIntersection() && !uncast.isIntersection()) {
const castParts = cast.types;
const otherPart = castParts.find(part => part !== uncast);
if (
tsutils.isTypeParameter(uncast) &&
castParts.length === 2 &&
castParts.some(part => part === uncast) &&
otherPart != null &&
isEmptyObjectType(otherPart) &&
!containsTypeVariable(otherPart)
) {
const constraint = checker.getBaseConstraintOfType(uncast);
if (constraint && !isNullableType(constraint)) {
return true;
}
}
return false;
}
if (
!hasSameProperties(uncast, cast) ||
!haveSameTypeArguments(uncast, cast)
) {
return false;
}
return areMutuallyAssignable(uncast, cast);
}
function isTypeLiteral(type: ts.Type): boolean {
return type.isLiteral() || tsutils.isBooleanLiteralType(type);
}
function hasIndexSignature(type: ts.Type): boolean {
return tsutils
.unionConstituents(type)
.some(part => checker.getIndexInfosOfType(part).length > 0);
}
function getTypeArguments(type: ts.Type): readonly ts.Type[] {
return (
type.aliasTypeArguments ??
(tsutils.isTypeReference(type) ? checker.getTypeArguments(type) : [])
);
}
function typeContains(
type: ts.Type,
predicate: (type: ts.Type) => boolean,
seen = new Set<ts.Type>(),
): boolean {
if (seen.has(type)) {
return false;
}
seen.add(type);
if (predicate(type)) {
return true;
}
if (type.isUnionOrIntersection()) {
return type.types.some(t => typeContains(t, predicate, seen));
}
const nestedTypes = [
...getTypeArguments(type),
...type
.getCallSignatures()
.flatMap(sig => [
sig.getReturnType(),
...sig.getParameters().map(p => checker.getTypeOfSymbol(p)),
]),
];
return nestedTypes.some(t => typeContains(t, predicate, seen));
}
function containsAny(type: ts.Type): boolean {
try {
return typeContains(type, t => isTypeFlagSet(t, ts.TypeFlags.Any));
} catch (error) {
// Workaround for https://github.com/typescript-eslint/typescript-eslint/issues/12705
if (
error instanceof RangeError &&
error.message === 'Maximum call stack size exceeded'
) {
return false;
}
throw error;
}
}
function containsTypeVariable(type: ts.Type): boolean {
return typeContains(type, t =>
isTypeFlagSet(t, ts.TypeFlags.TypeVariable | ts.TypeFlags.Index),
);
}
function hasPhantomTypeArguments(type: ts.Type): boolean {
return isEmptyObjectType(type) && getTypeArguments(type).length > 0;
}
function hasTypeParams(sig: ts.Signature): boolean {
return (sig.getTypeParameters()?.length ?? 0) > 0;
}
function genericsMismatch(uncast: ts.Type, contextual: ts.Type): boolean {
return contextual.getProperties().some(prop => {
const contextualSigs = checker.getSignaturesOfType(
checker.getTypeOfSymbol(prop),
ts.SignatureKind.Call,
);
if (!contextualSigs.some(hasTypeParams)) {
return false;
}
const uncastProp = uncast.getProperty(prop.getEscapedName() as string);
if (!uncastProp) {
return true;
}
return !checker
.getSignaturesOfType(
checker.getTypeOfSymbol(uncastProp),
ts.SignatureKind.Call,
)
.some(hasTypeParams);
});
}
function hasSameProperties(uncast: ts.Type, cast: ts.Type): boolean {
const uncastProps = uncast.getProperties();
const castProps = cast.getProperties();
if (uncastProps.length !== castProps.length) {
return false;
}
const castPropNames = new Set(castProps.map(p => p.getEscapedName()));
return uncastProps.every(prop => {
const name = prop.getEscapedName();
return (
castPropNames.has(name) &&
tsutils.isPropertyReadonlyInType(uncast, name, checker) ===
tsutils.isPropertyReadonlyInType(cast, name, checker)
);
});
}
function haveSameTypeArguments(uncast: ts.Type, cast: ts.Type): boolean {
const uncastArgs = getTypeArguments(uncast);
const castArgs = getTypeArguments(cast);
return (
uncastArgs.length === castArgs.length &&
uncastArgs.every((arg, i) => arg === castArgs[i])
);
}
function areMutuallyAssignable(a: ts.Type, b: ts.Type): boolean {
return (
checker.isTypeAssignableTo(a, b) && checker.isTypeAssignableTo(b, a)
);
}
function areUnionPartsEquivalentIgnoringUndefined(
uncast: ts.Type,
cast: ts.Type,
): boolean {
const filterUndefined = (part: ts.Type): boolean =>
!isTypeFlagSet(part, ts.TypeFlags.Undefined);
const uncastParts = tsutils
.unionConstituents(uncast)
.filter(filterUndefined);
const castParts = tsutils.unionConstituents(cast).filter(filterUndefined);
if (uncastParts.length !== castParts.length) {
return false;
}
const uncastPartsSet = new Set(uncastParts);
return castParts.every(part => uncastPartsSet.has(part));
}
function getOriginalExpression(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
): TSESTree.Expression {
let current = node.expression;
while (
current.type === AST_NODE_TYPES.TSAsExpression ||
current.type === AST_NODE_TYPES.TSTypeAssertion
) {
current = current.expression;
}
return current;
}
function isDoubleAssertionUnnecessary(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
contextualType: ts.Type | undefined,
): false | MessageIds {
const innerExpression = node.expression;
if (
innerExpression.type !== AST_NODE_TYPES.TSAsExpression &&
innerExpression.type !== AST_NODE_TYPES.TSTypeAssertion
) {
return false;
}
const originalExpr = getOriginalExpression(node);
const originalType = services.getTypeAtLocation(originalExpr);
const castType = services.getTypeAtLocation(node);
if (
isTypeUnchanged(node, innerExpression, originalType, castType) &&
!isTypeFlagSet(castType, ts.TypeFlags.Any)
) {
return 'unnecessaryAssertion';
}
if (contextualType) {
const intermediateType = services.getTypeAtLocation(innerExpression);
if (
(isTypeFlagSet(intermediateType, ts.TypeFlags.Any) ||
isTypeFlagSet(intermediateType, ts.TypeFlags.Unknown)) &&
checker.isTypeAssignableTo(originalType, contextualType)
) {
return 'contextuallyUnnecessary';
}
}
return false;
}
const CONCEPTUALLY_LITERAL_TYPES = new Set([
AST_NODE_TYPES.Literal,
AST_NODE_TYPES.ArrayExpression,
AST_NODE_TYPES.ObjectExpression,
AST_NODE_TYPES.TemplateLiteral,
AST_NODE_TYPES.ClassExpression,
AST_NODE_TYPES.FunctionExpression,
AST_NODE_TYPES.ArrowFunctionExpression,
AST_NODE_TYPES.JSXElement,
AST_NODE_TYPES.JSXFragment,
]);
function isConceptuallyLiteral(node: TSESTree.Node): boolean {
return CONCEPTUALLY_LITERAL_TYPES.has(node.type);
}
function isIIFE(
expression: TSESTree.Expression,
): expression is TSESTree.CallExpression & {
callee: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression;
} {
return (
expression.type === AST_NODE_TYPES.CallExpression &&
(expression.callee.type === AST_NODE_TYPES.ArrowFunctionExpression ||
expression.callee.type === AST_NODE_TYPES.FunctionExpression)
);
}
function isEmptyObjectType(type: ts.Type): boolean {
return (
isTypeFlagSet(type, ts.TypeFlags.NonPrimitive) ||
(type.getProperties().length === 0 &&
!type.getCallSignatures().length &&
!type.getConstructSignatures().length &&
!type.getStringIndexType() &&
!type.getNumberIndexType())
);
}
function hasGenericCallSignature(type: ts.Type): boolean {
return type.getCallSignatures().some(hasTypeParams);
}
function isArgumentToOverloadedFunction(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
): boolean {
const { parent } = node;
if (
(parent.type !== AST_NODE_TYPES.CallExpression &&
parent.type !== AST_NODE_TYPES.NewExpression) ||
!parent.arguments.includes(node)
) {
return false;
}
// An optional-chained callee (`foo?.bar(...)`) types as `<method> | undefined`,
// and a union exposes no call signatures β strip the nullability first.
const calleeType = services
.getTypeAtLocation(parent.callee)
.getNonNullableType();
const signatures = calleeType.getCallSignatures();
if (signatures.length <= 1) {
return false;
}
const argIndex = parent.arguments.indexOf(node);
const paramTypes = signatures.map(sig => {
const params = sig.getParameters();
if (argIndex >= params.length) {
return undefined;
}
const param = params[argIndex];
let paramType = checker.getTypeOfSymbol(param);
if (
param.valueDeclaration &&
ts.isParameter(param.valueDeclaration) &&
param.valueDeclaration.dotDotDotToken
) {
const typeArgs = getTypeArguments(paramType);
if (typeArgs.length > 0) {
paramType = typeArgs[0];
}
}
return paramType;
});
if (paramTypes.some(type => type == null)) {
return true;
}
const definedParamTypes = paramTypes as ts.Type[];
const firstParamType = definedParamTypes[0];
if (definedParamTypes.every(type => type === firstParamType)) {
return false;
}
const uncastType = services.getTypeAtLocation(node.expression);
return !definedParamTypes.every(type =>
checker.isTypeAssignableTo(uncastType, type),
);
}
function isInDestructuringDeclaration(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
): boolean {
const { parent } = node;
return (
parent.type === AST_NODE_TYPES.VariableDeclarator &&
parent.init === node &&
(parent.id.type === AST_NODE_TYPES.ObjectPattern ||
parent.id.type === AST_NODE_TYPES.ArrayPattern)
);
}
function isPropertyInProblematicContext(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
): boolean {
const { parent } = node;
if (parent.type !== AST_NODE_TYPES.Property || parent.value !== node) {
return false;
}
const objectExpr = parent.parent;
if (objectExpr.type !== AST_NODE_TYPES.ObjectExpression) {
return false;
}
const objectTsNode = services.esTreeNodeToTSNodeMap.get(objectExpr);
if (checker.getContextualType(objectTsNode)?.isUnion()) {
const nodeTsNode = services.esTreeNodeToTSNodeMap.get(node);
const propContextualType = checker.getContextualType(nodeTsNode);
if (propContextualType == null) {
return true;
}
const nonNullableContextualType =
checker.getNonNullableType(propContextualType);
if (nonNullableContextualType.isUnion()) {
return true;
}
const uncastType = services.getTypeAtLocation(node.expression);
return !checker.isTypeAssignableTo(
uncastType,
nonNullableContextualType,
);
}
const objectParent = objectExpr.parent;
return (
objectParent.type === AST_NODE_TYPES.TSSatisfiesExpression ||
(objectParent.type === AST_NODE_TYPES.CallExpression &&
objectParent.parent.type === AST_NODE_TYPES.TSSatisfiesExpression)
);
}
function isAssignmentInNonStatementContext(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
): boolean {
const { parent } = node;
if (
parent.type !== AST_NODE_TYPES.AssignmentExpression ||
parent.right !== node
) {
return false;
}
const assignmentParent = parent.parent;
return assignmentParent.type !== AST_NODE_TYPES.ExpressionStatement;
}
function isRightHandSideOfLogicalAssignment(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
): boolean {
const { parent } = node;
return (
parent.type === AST_NODE_TYPES.AssignmentExpression &&
parent.right === node &&
(parent.operator === '&&=' ||
parent.operator === '||=' ||
parent.operator === '??=')
);
}
function isInGenericContext(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
): boolean {
let seenFunction = false;
for (
let current: TSESTree.Node | undefined = node.parent;
current;
current = current.parent
) {
if (current.type === AST_NODE_TYPES.FunctionDeclaration) {
return false;
}
if (
current.type === AST_NODE_TYPES.FunctionExpression ||
current.type === AST_NODE_TYPES.ArrowFunctionExpression
) {
if (current.body.type === AST_NODE_TYPES.BlockStatement) {
return false;
}
if (seenFunction) {
return false;
}
seenFunction = true;
}
if (
current.type === AST_NODE_TYPES.CallExpression ||
current.type === AST_NODE_TYPES.NewExpression
) {
if (current.typeArguments != null) {
continue;
}
if (
current.type === AST_NODE_TYPES.CallExpression &&
current.callee.type === AST_NODE_TYPES.MemberExpression &&
current.arguments.includes(node)
) {
continue;
}
const calleeType = checker.getTypeAtLocation(
services.esTreeNodeToTSNodeMap.get(current.callee),
);
if (hasGenericCallSignature(calleeType)) {
return true;
}
}
}
return false;
}
function hasPhantomTypeArgumentMismatch(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
uncastType: ts.Type,
contextualType: ts.Type,
): boolean {
return (
isInGenericContext(node) &&
(hasPhantomTypeArguments(uncastType) ||
hasPhantomTypeArguments(contextualType)) &&
!haveSameTypeArguments(uncastType, contextualType)
);
}
const SKIP_PARENT_TYPES = new Set([
AST_NODE_TYPES.TSAsExpression,
AST_NODE_TYPES.TSTypeAssertion,
AST_NODE_TYPES.SpreadElement,
AST_NODE_TYPES.TSSatisfiesExpression,
]);
function shouldSkipContextualTypeFallback(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
castIsAny: boolean,
): boolean {
if (castIsAny) {
return (
node.parent.type === AST_NODE_TYPES.LogicalExpression ||
isInGenericContext(node)
);
}
/**
* Interpolated template literals can be widened to `string` while contextual
* typing still accepts them, so the assertion may be required.
* @see https://github.com/typescript-eslint/typescript-eslint/issues/12276
*/
if (isTemplateLiteralWithExpressions(node.expression)) {
return true;
}
if (
SKIP_PARENT_TYPES.has(node.parent.type) ||
node.expression.type === AST_NODE_TYPES.ArrayExpression ||
isInDestructuringDeclaration(node) ||
isPropertyInProblematicContext(node) ||
isAssignmentInNonStatementContext(node) ||
isRightHandSideOfLogicalAssignment(node) ||
isArgumentToOverloadedFunction(node)
) {
return true;
}
if (isInGenericContext(node)) {
const originalExpr = getOriginalExpression(node);
return (
!isConceptuallyLiteral(originalExpr) &&
node.parent.type !== AST_NODE_TYPES.Property
);
}
return false;
}
function getUncastType(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
): ts.Type {
// Special handling for IIFE: extract the function's return type
if (isIIFE(node.expression)) {
const callee = node.expression.callee;
const functionType = services.getTypeAtLocation(callee);
const signatures = functionType.getCallSignatures();
if (signatures.length > 0) {
const returnType = checker.getReturnTypeOfSignature(signatures[0]);
// If the function has no explicit return type annotation and returns undefined,
// treat it as void (TypeScript infers () => {} as () => undefined, but it should be void)
if (
callee.returnType == null &&
isTypeFlagSet(returnType, ts.TypeFlags.Undefined)
) {
return checker.getVoidType();
}
return returnType;
}
}
return services.getTypeAtLocation(node.expression);
}
function createAssertionFixer(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
): ReportFixFunction {
return fixer => {
if (node.type === AST_NODE_TYPES.TSTypeAssertion) {
const openingAngleBracket = nullThrows(
context.sourceCode.getTokenBefore(
node.typeAnnotation,
token =>
token.type === AST_TOKEN_TYPES.Punctuator &&
token.value === '<',
),
NullThrowsReasons.MissingToken('<', 'type annotation'),
);
const closingAngleBracket = nullThrows(
context.sourceCode.getTokenAfter(
node.typeAnnotation,
token =>
token.type === AST_TOKEN_TYPES.Punctuator &&
token.value === '>',
),
NullThrowsReasons.MissingToken('>', 'type annotation'),
);
// Removing the angle brackets leaves the asserted operand at the
// assertion's position, so its first token leads whatever the
// assertion led. A leading `{`/`function`/`class` at the start of an
// expression statement is parsed as a block / function or class
// declaration, and a leading `{` at the start of a concise arrow body
// is parsed as a block body. In those positions the operand must be
// wrapped in parentheses to stay an expression.
const firstOperandToken = nullThrows(
context.sourceCode.getTokenAfter(closingAngleBracket),
NullThrowsReasons.MissingToken('operand', 'type assertion'),
);
const breaksExpressionStatement =
['{', 'function', 'class'].includes(firstOperandToken.value) &&
isStartOfExpressionStatement(node);
const breaksArrowFunctionBody =
firstOperandToken.value === '{' &&
isStartOfArrowFunctionBody(node, context.sourceCode);
const needsParens =
breaksExpressionStatement || breaksArrowFunctionBody;
const fixes: RuleFix[] = [];
if (needsParens) {
fixes.push(fixer.insertTextBefore(node, '('));
}
fixes.push(
fixer.removeRange([
openingAngleBracket.range[0],
closingAngleBracket.range[1],
]),
);
if (needsParens) {
fixes.push(fixer.insertTextAfter(node, ')'));
}
return fixes;
}
const asToken = nullThrows(
context.sourceCode.getTokenAfter(
node.expression,
token =>
token.type === AST_TOKEN_TYPES.Identifier && token.value === 'as',
),
NullThrowsReasons.MissingToken('>', 'type annotation'),
);
const tokenBeforeAs = nullThrows(
context.sourceCode.getTokenBefore(asToken, {
includeComments: true,
}),
NullThrowsReasons.MissingToken('comment', 'as'),
);
return fixer.removeRange([tokenBeforeAs.range[1], node.range[1]]);
};
}
function reportDoubleAssertionIfUnnecessary(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
contextualType: ts.Type | undefined,
): void {
const doubleAssertionResult = isDoubleAssertionUnnecessary(
node,
contextualType,
);
if (doubleAssertionResult) {
context.report({
node,
messageId: doubleAssertionResult,
fix(fixer) {
const originalExpr = getOriginalExpression(node);
let text = context.sourceCode.getText(originalExpr);
if (
originalExpr.type === AST_NODE_TYPES.ObjectExpression &&
node.parent.type === AST_NODE_TYPES.ArrowFunctionExpression &&
node.parent.body === node
) {
text = `(${text})`;
}
return fixer.replaceText(node, text);
},
});
}
}
return {
'TSAsExpression, TSTypeAssertion'(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
): void {
if (
options.typesToIgnore?.includes(
context.sourceCode.getText(node.typeAnnotation),
)
) {
return;
}
const castType = services.getTypeAtLocation(node);
const castTypeIsLiteral = isTypeLiteral(castType);
const typeAnnotationIsConstAssertion = isConstAssertion(
node.typeAnnotation,
);
if (
!options.checkLiteralConstAssertions &&
castTypeIsLiteral &&
typeAnnotationIsConstAssertion
) {
return;
}
const uncastType = getUncastType(node);
const typeIsUnchanged = isTypeUnchanged(
node,
node.expression,
uncastType,
castType,
);
const wouldSameTypeBeInferred = castTypeIsLiteral
? isImplicitlyNarrowedLiteralDeclaration(node)
: !typeAnnotationIsConstAssertion;
if (typeIsUnchanged && wouldSameTypeBeInferred) {
context.report({
node,
messageId: 'unnecessaryAssertion',
fix: createAssertionFixer(node),
});
return;
}
const originalNode = services.esTreeNodeToTSNodeMap.get(node);
const castIsAny =
isTypeFlagSet(castType, ts.TypeFlags.Any) &&
!SKIP_PARENT_TYPES.has(node.parent.type);
const contextualType = shouldSkipContextualTypeFallback(node, castIsAny)
? undefined
: checker.getContextualType(originalNode);
if (contextualType) {
const contextualTypeIsAny = isTypeFlagSet(
contextualType,
ts.TypeFlags.Any,
);
const isCallArgument =
(node.parent.type === AST_NODE_TYPES.CallExpression ||
node.parent.type === AST_NODE_TYPES.NewExpression) &&
node.parent.arguments.includes(node);
const anyInvolvedInContextualCheck = contextualTypeIsAny
? isCallArgument && !containsAny(castType)
: !containsAny(contextualType);
const isNullishLiteralToUnion =
castType.isUnion() &&
((node.expression.type === AST_NODE_TYPES.Literal &&
node.expression.value == null) ||
(node.expression.type === AST_NODE_TYPES.Identifier &&
node.expression.name === 'undefined'));
const isContextuallyUnnecessary =
!typeAnnotationIsConstAssertion &&
!containsAny(uncastType) &&
anyInvolvedInContextualCheck &&
!hasPhantomTypeArgumentMismatch(node, uncastType, contextualType) &&
(castIsAny || !genericsMismatch(uncastType, contextualType)) &&
(contextualTypeIsAny ||
checker.isTypeAssignableTo(uncastType, contextualType)) &&
!isNullishLiteralToUnion;
if (isContextuallyUnnecessary) {
context.report({
node,
messageId: 'contextuallyUnnecessary',
fix: createAssertionFixer(node),
});
return;
}
}
reportDoubleAssertionIfUnnecessary(node, contextualType);
},
TSNonNullExpression(node): void {
const removeExclamationFix: ReportFixFunction = fixer => {
const exclamationToken = nullThrows(
context.sourceCode.getLastToken(node, token => token.value === '!'),
NullThrowsReasons.MissingToken(
'exclamation mark',
'non-null assertion',
),
);
return fixer.removeRange(exclamationToken.range);
};
if (
node.parent.type === AST_NODE_TYPES.AssignmentExpression &&
node.parent.operator === '='
) {
if (node.parent.left === node) {
context.report({
node,
messageId: 'contextuallyUnnecessary',
fix: removeExclamationFix,
});
}
// for all other = assignments we ignore non-null checks
// this is because non-null assertions can change the type-flow of the code
// so whilst they might be unnecessary for the assignment - they are necessary
// for following code
return;
}
const originalNode = services.esTreeNodeToTSNodeMap.get(node);
const constrainedType = getConstrainedTypeAtLocation(
services,
node.expression,
);
const actualType = services.getTypeAtLocation(node.expression);
// Check both the constrained type and the actual type.
// If either is nullable, we should not report the assertion as unnecessary.
// This handles cases like generic constraints with `any` where the
// constrained type is `any` (nullable) but the actual type might be
// a type parameter that TypeScript treats nominally.
// See: https://github.com/typescript-eslint/typescript-eslint/issues/11559
const constrainedTypeIsNullable = isNullableType(constrainedType);
const actualTypeIsNullable = isNullableType(actualType);
if (!constrainedTypeIsNullable && !actualTypeIsNullable) {
if (
node.expression.type === AST_NODE_TYPES.Identifier &&
isPossiblyUsedBeforeAssigned(node.expression)
) {
return;
}
context.report({
node,
messageId: 'unnecessaryAssertion',
fix: removeExclamationFix,
});
} else {
// we know it's a nullable type
// so figure out if the variable is used in a place that accepts nullable types
// If the constrained type differs from the actual type (e.g., when dealing
// with unresolved generic type parameters), we should not report the assertion
// as contextually unnecessary. TypeScript may still require the assertion
// even if the constraint is nullable (like `any`).
// See: https://github.com/typescript-eslint/typescript-eslint/issues/11559
if (constrainedType !== actualType) {
return;
}
const contextualType = getContextualType(checker, originalNode);
if (contextualType) {
if (
isTypeFlagSet(constrainedType, ts.TypeFlags.Unknown) &&
!isTypeFlagSet(contextualType, ts.TypeFlags.Unknown)
) {
return;
}
// in strict mode you can't assign null to undefined, so we have to make sure that
// the two types share a nullable type
const typeIncludesUndefined = isTypeFlagSet(
constrainedType,
ts.TypeFlags.Undefined,
);
const typeIncludesNull = isTypeFlagSet(
constrainedType,
ts.TypeFlags.Null,
);
const typeIncludesVoid = isTypeFlagSet(
constrainedType,
ts.TypeFlags.Void,
);
const contextualTypeIncludesUndefined = isTypeFlagSet(
contextualType,
ts.TypeFlags.Undefined,
);
const contextualTypeIncludesNull = isTypeFlagSet(
contextualType,
ts.TypeFlags.Null,
);
const contextualTypeIncludesVoid = isTypeFlagSet(
contextualType,
ts.TypeFlags.Void,
);
// make sure that the parent accepts the same types
// i.e. assigning `string | null | undefined` to `string | undefined` is invalid
const isValidUndefined = typeIncludesUndefined
? contextualTypeIncludesUndefined
: true;
const isValidNull = typeIncludesNull
? contextualTypeIncludesNull
: true;
const isValidVoid = typeIncludesVoid
? contextualTypeIncludesVoid
: true;
if (isValidUndefined && isValidNull && isValidVoid) {
context.report({
node,
messageId: 'contextuallyUnnecessary',
fix: removeExclamationFix,
});
}
}
}
},
};
}
Internal helpers¶
Declared inside another function in this file.
isPossiblyUsedBeforeAssigned(node: TSESTree.Expression): boolean¶
Returns true if there's a chance the variable has been used before a value has been assigned to it
Raw JSDoc
Calls:
getDeclaration (from ../util)tsutils.isStrictCompilerOptionEnabledts.isVariableDeclarationts.isVariableDeclarationListservices.tsNodeToESTreeNodeMap.hasservices.tsNodeToESTreeNodeMap.getcontext.sourceCode.getScopechecker.getTypeFromTypeNodegetConstrainedTypeAtLocation (from ../util)ts.isVariableStatementtsutils.includesModifiergetModifiers (from ../util)
Internal Comments:
// don't know what the declaration is for some reason, so just assume the worst
// non-strict mode doesn't care about used before assigned errors (x4)
// ignore class properties as they are compile time guarded (x3)
// also ignore function arguments as they can't be used before defined (x3)
// For var declarations, we need to check whether the node
// is actually in a descendant of its declaration or not. If not,
// it may be used before defined.
// eg
// if (Math.random() < 0.5) {
// var x: number = 2;
// } else {
// x!.toFixed();
// }
// var (x4)
// If they are not in the same file it will not exist. (x4)
// This situation must not occur using before defined. (x4)
// is it `const x!: number` (x5)
// check if the defined variable type has changed since assignment (x2)
// `declare`s are never narrowed, so never skip them
// possibly used before assigned, so just skip it
// better to false negative and skip it, than false positive and fix to compile erroring code
//
// no better way to figure this out right now
// https://github.com/Microsoft/TypeScript/issues/31124
Code
function isPossiblyUsedBeforeAssigned(node: TSESTree.Expression): boolean {
const declaration = getDeclaration(services, node);
if (!declaration) {
// don't know what the declaration is for some reason, so just assume the worst
return true;
}
if (
// non-strict mode doesn't care about used before assigned errors
tsutils.isStrictCompilerOptionEnabled(
compilerOptions,
'strictNullChecks',
) &&
// ignore class properties as they are compile time guarded
// also ignore function arguments as they can't be used before defined
ts.isVariableDeclaration(declaration)
) {
// For var declarations, we need to check whether the node
// is actually in a descendant of its declaration or not. If not,
// it may be used before defined.
// eg
// if (Math.random() < 0.5) {
// var x: number = 2;
// } else {
// x!.toFixed();
// }
if (
ts.isVariableDeclarationList(declaration.parent) &&
// var
declaration.parent.flags === ts.NodeFlags.None &&
// If they are not in the same file it will not exist.
// This situation must not occur using before defined.
services.tsNodeToESTreeNodeMap.has(declaration)
) {
const declaratorNode: TSESTree.VariableDeclaration =
services.tsNodeToESTreeNodeMap.get(declaration);
const scope = context.sourceCode.getScope(node);
const declaratorScope = context.sourceCode.getScope(declaratorNode);
let parentScope: Scope | null = declaratorScope;
while ((parentScope = parentScope.upper)) {
if (parentScope === scope) {
return true;
}
}
}
if (
// is it `const x!: number`
declaration.initializer == null &&
declaration.exclamationToken == null &&
declaration.type != null
) {
// check if the defined variable type has changed since assignment
const declarationType = checker.getTypeFromTypeNode(declaration.type);
const type = getConstrainedTypeAtLocation(services, node);
if (
declarationType === type &&
// `declare`s are never narrowed, so never skip them
!(
ts.isVariableDeclarationList(declaration.parent) &&
ts.isVariableStatement(declaration.parent.parent) &&
tsutils.includesModifier(
getModifiers(declaration.parent.parent),
ts.SyntaxKind.DeclareKeyword,
)
)
) {
// possibly used before assigned, so just skip it
// better to false negative and skip it, than false positive and fix to compile erroring code
//
// no better way to figure this out right now
// https://github.com/Microsoft/TypeScript/issues/31124
return true;
}
}
}
return false;
}
isConstAssertion(node: TSESTree.TypeNode): boolean¶
Parameters:
nodeTSESTree.TypeNode
Returns: boolean
Code
isTemplateLiteralWithExpressions(expression: TSESTree.Expression): boolean¶
Parameters:
expressionTSESTree.Expression
Returns: boolean
Code
isImplicitlyNarrowedLiteralDeclaration({ expression, parent, }: TSESTree.TSAsExpression | TSESTree.TSTyβ¦): boolean¶
Parameters:
{ expression, parent, }TSESTree.TSAsExpression | TSESTree.TSTypeAssertion
Returns: boolean
Calls:
isTemplateLiteralWithExpressions
Internal Comments:
/**
* Even on `const` variable declarations, template literals with expressions can sometimes be widened without a type assertion.
* @see https://github.com/typescript-eslint/typescript-eslint/issues/8737
*/
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion (x2)
Code
function isImplicitlyNarrowedLiteralDeclaration({
expression,
parent,
}: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion): boolean {
/**
* Even on `const` variable declarations, template literals with expressions can sometimes be widened without a type assertion.
* @see https://github.com/typescript-eslint/typescript-eslint/issues/8737
*/
if (isTemplateLiteralWithExpressions(expression)) {
return false;
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const maybeDeclarationNode = parent.parent!;
return (
(maybeDeclarationNode.type === AST_NODE_TYPES.VariableDeclaration &&
maybeDeclarationNode.kind === 'const') ||
(parent.type === AST_NODE_TYPES.PropertyDefinition && parent.readonly)
);
}
isTypeUnchanged(node: TSESTree.TSAsExpression | TSESTree.TSTyβ¦, expression: TSESTree.Expression, uncast: ts.Type, cast: ts.Type): boolean¶
Parameters:
nodeTSESTree.TSAsExpression | TSESTree.TSTypeAssertionexpressionTSESTree.Expressionuncastts.Typecastts.Type
Returns: boolean
Calls:
containsTypeVariableisTypeFlagSet (from ../util)tsutils.isCompilerOptionEnabledareUnionPartsEquivalentIgnoringUndefinedhasIndexSignaturecontainsAnyisConceptuallyLiteralcast .getProperties() .someisTypeLiteralchecker.getTypeOfSymbolcast.isIntersectionuncast.isIntersectioncastParts.findtsutils.isTypeParametercastParts.someisEmptyObjectTypechecker.getBaseConstraintOfTypeisNullableType (from ../util)hasSamePropertieshaveSameTypeArgumentsareMutuallyAssignable
Code
function isTypeUnchanged(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
expression: TSESTree.Expression,
uncast: ts.Type,
cast: ts.Type,
): boolean {
if (uncast === cast) {
return true;
}
if (
node.typeAnnotation.type === AST_NODE_TYPES.TSIntersectionType &&
containsTypeVariable(cast)
) {
return false;
}
if (
isTypeFlagSet(uncast, ts.TypeFlags.Undefined) &&
isTypeFlagSet(cast, ts.TypeFlags.Undefined) &&
tsutils.isCompilerOptionEnabled(
compilerOptions,
'exactOptionalPropertyTypes',
)
) {
return areUnionPartsEquivalentIgnoringUndefined(uncast, cast);
}
if (
(isTypeFlagSet(uncast, ts.TypeFlags.NonPrimitive) &&
!isTypeFlagSet(cast, ts.TypeFlags.NonPrimitive)) ||
(hasIndexSignature(uncast) && !hasIndexSignature(cast)) ||
containsAny(uncast) ||
containsAny(cast) ||
(containsTypeVariable(cast) && !containsTypeVariable(uncast))
) {
return false;
}
if (
isConceptuallyLiteral(expression) &&
(expression.type !== AST_NODE_TYPES.ObjectExpression ||
expression.properties.length === 0 ||
cast
.getProperties()
.some(p => isTypeLiteral(checker.getTypeOfSymbol(p))))
) {
return false;
}
if (cast.isIntersection() && !uncast.isIntersection()) {
const castParts = cast.types;
const otherPart = castParts.find(part => part !== uncast);
if (
tsutils.isTypeParameter(uncast) &&
castParts.length === 2 &&
castParts.some(part => part === uncast) &&
otherPart != null &&
isEmptyObjectType(otherPart) &&
!containsTypeVariable(otherPart)
) {
const constraint = checker.getBaseConstraintOfType(uncast);
if (constraint && !isNullableType(constraint)) {
return true;
}
}
return false;
}
if (
!hasSameProperties(uncast, cast) ||
!haveSameTypeArguments(uncast, cast)
) {
return false;
}
return areMutuallyAssignable(uncast, cast);
}
isTypeLiteral(type: ts.Type): boolean¶
Parameters:
typets.Type
Returns: boolean
Calls:
type.isLiteraltsutils.isBooleanLiteralType
Code
hasIndexSignature(type: ts.Type): boolean¶
Parameters:
typets.Type
Returns: boolean
Calls:
tsutils .unionConstituents(type) .somechecker.getIndexInfosOfType
Code
getTypeArguments(type: ts.Type): readonly ts.Type[]¶
Parameters:
typets.Type
Returns: readonly ts.Type[]
Calls:
tsutils.isTypeReferencechecker.getTypeArguments
Code
typeContains(type: ts.Type, predicate: (type: ts.Type) => boolean, seen: Set<ts.Type>): boolean¶
Parameters:
typets.Typepredicate(type: ts.Type) => booleanseenSet<ts.Type>
Returns: boolean
Calls:
seen.hasseen.addpredicatetype.isUnionOrIntersectiontype.types.sometypeContainsgetTypeArgumentstype .getCallSignatures() .flatMapsig.getReturnTypesig.getParameters().mapchecker.getTypeOfSymbolnestedTypes.some
Code
function typeContains(
type: ts.Type,
predicate: (type: ts.Type) => boolean,
seen = new Set<ts.Type>(),
): boolean {
if (seen.has(type)) {
return false;
}
seen.add(type);
if (predicate(type)) {
return true;
}
if (type.isUnionOrIntersection()) {
return type.types.some(t => typeContains(t, predicate, seen));
}
const nestedTypes = [
...getTypeArguments(type),
...type
.getCallSignatures()
.flatMap(sig => [
sig.getReturnType(),
...sig.getParameters().map(p => checker.getTypeOfSymbol(p)),
]),
];
return nestedTypes.some(t => typeContains(t, predicate, seen));
}
containsAny(type: ts.Type): boolean¶
Parameters:
typets.Type
Returns: boolean
Calls:
typeContainsisTypeFlagSet (from ../util)
Internal Comments:
Code
function containsAny(type: ts.Type): boolean {
try {
return typeContains(type, t => isTypeFlagSet(t, ts.TypeFlags.Any));
} catch (error) {
// Workaround for https://github.com/typescript-eslint/typescript-eslint/issues/12705
if (
error instanceof RangeError &&
error.message === 'Maximum call stack size exceeded'
) {
return false;
}
throw error;
}
}
containsTypeVariable(type: ts.Type): boolean¶
Parameters:
typets.Type
Returns: boolean
Calls:
typeContainsisTypeFlagSet (from ../util)
Code
hasPhantomTypeArguments(type: ts.Type): boolean¶
Parameters:
typets.Type
Returns: boolean
Calls:
isEmptyObjectTypegetTypeArguments
Code
hasTypeParams(sig: ts.Signature): boolean¶
Parameters:
sigts.Signature
Returns: boolean
Calls:
sig.getTypeParameters
Code
genericsMismatch(uncast: ts.Type, contextual: ts.Type): boolean¶
Parameters:
uncastts.Typecontextualts.Type
Returns: boolean
Calls:
contextual.getProperties().somechecker.getSignaturesOfTypechecker.getTypeOfSymbolcontextualSigs.someuncast.getPropertyprop.getEscapedNamechecker .getSignaturesOfType( checker.getTypeOfSymbol(uncastProp), ts.SignatureKind.Call, ) .some
Code
function genericsMismatch(uncast: ts.Type, contextual: ts.Type): boolean {
return contextual.getProperties().some(prop => {
const contextualSigs = checker.getSignaturesOfType(
checker.getTypeOfSymbol(prop),
ts.SignatureKind.Call,
);
if (!contextualSigs.some(hasTypeParams)) {
return false;
}
const uncastProp = uncast.getProperty(prop.getEscapedName() as string);
if (!uncastProp) {
return true;
}
return !checker
.getSignaturesOfType(
checker.getTypeOfSymbol(uncastProp),
ts.SignatureKind.Call,
)
.some(hasTypeParams);
});
}
hasSameProperties(uncast: ts.Type, cast: ts.Type): boolean¶
Parameters:
uncastts.Typecastts.Type
Returns: boolean
Calls:
uncast.getPropertiescast.getPropertiescastProps.mapp.getEscapedNameuncastProps.everyprop.getEscapedNamecastPropNames.hastsutils.isPropertyReadonlyInType
Code
function hasSameProperties(uncast: ts.Type, cast: ts.Type): boolean {
const uncastProps = uncast.getProperties();
const castProps = cast.getProperties();
if (uncastProps.length !== castProps.length) {
return false;
}
const castPropNames = new Set(castProps.map(p => p.getEscapedName()));
return uncastProps.every(prop => {
const name = prop.getEscapedName();
return (
castPropNames.has(name) &&
tsutils.isPropertyReadonlyInType(uncast, name, checker) ===
tsutils.isPropertyReadonlyInType(cast, name, checker)
);
});
}
haveSameTypeArguments(uncast: ts.Type, cast: ts.Type): boolean¶
Parameters:
uncastts.Typecastts.Type
Returns: boolean
Calls:
getTypeArgumentsuncastArgs.every
Code
areMutuallyAssignable(a: ts.Type, b: ts.Type): boolean¶
Parameters:
ats.Typebts.Type
Returns: boolean
Calls:
checker.isTypeAssignableTo
Code
areUnionPartsEquivalentIgnoringUndefined(uncast: ts.Type, cast: ts.Type): boolean¶
Parameters:
uncastts.Typecastts.Type
Returns: boolean
Calls:
isTypeFlagSet (from ../util)tsutils .unionConstituents(uncast) .filtertsutils.unionConstituents(cast).filtercastParts.everyuncastPartsSet.has
Code
function areUnionPartsEquivalentIgnoringUndefined(
uncast: ts.Type,
cast: ts.Type,
): boolean {
const filterUndefined = (part: ts.Type): boolean =>
!isTypeFlagSet(part, ts.TypeFlags.Undefined);
const uncastParts = tsutils
.unionConstituents(uncast)
.filter(filterUndefined);
const castParts = tsutils.unionConstituents(cast).filter(filterUndefined);
if (uncastParts.length !== castParts.length) {
return false;
}
const uncastPartsSet = new Set(uncastParts);
return castParts.every(part => uncastPartsSet.has(part));
}
filterUndefined(part: ts.Type): boolean¶
Parameters:
partts.Type
Returns: boolean
getOriginalExpression(node: TSESTree.TSAsExpression | TSESTree.TSTyβ¦): TSESTree.Expression¶
Parameters:
nodeTSESTree.TSAsExpression | TSESTree.TSTypeAssertion
Returns: TSESTree.Expression
Code
function getOriginalExpression(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
): TSESTree.Expression {
let current = node.expression;
while (
current.type === AST_NODE_TYPES.TSAsExpression ||
current.type === AST_NODE_TYPES.TSTypeAssertion
) {
current = current.expression;
}
return current;
}
isDoubleAssertionUnnecessary(node: TSESTree.TSAsExpression | TSESTree.TSTyβ¦, contextualType: ts.Type | undefined): false | MessageIds¶
Parameters:
nodeTSESTree.TSAsExpression | TSESTree.TSTypeAssertioncontextualTypets.Type | undefined
Returns: false | MessageIds
Calls:
getOriginalExpressionservices.getTypeAtLocationisTypeUnchangedisTypeFlagSet (from ../util)checker.isTypeAssignableTo
Code
function isDoubleAssertionUnnecessary(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
contextualType: ts.Type | undefined,
): false | MessageIds {
const innerExpression = node.expression;
if (
innerExpression.type !== AST_NODE_TYPES.TSAsExpression &&
innerExpression.type !== AST_NODE_TYPES.TSTypeAssertion
) {
return false;
}
const originalExpr = getOriginalExpression(node);
const originalType = services.getTypeAtLocation(originalExpr);
const castType = services.getTypeAtLocation(node);
if (
isTypeUnchanged(node, innerExpression, originalType, castType) &&
!isTypeFlagSet(castType, ts.TypeFlags.Any)
) {
return 'unnecessaryAssertion';
}
if (contextualType) {
const intermediateType = services.getTypeAtLocation(innerExpression);
if (
(isTypeFlagSet(intermediateType, ts.TypeFlags.Any) ||
isTypeFlagSet(intermediateType, ts.TypeFlags.Unknown)) &&
checker.isTypeAssignableTo(originalType, contextualType)
) {
return 'contextuallyUnnecessary';
}
}
return false;
}
isConceptuallyLiteral(node: TSESTree.Node): boolean¶
Parameters:
nodeTSESTree.Node
Returns: boolean
Calls:
CONCEPTUALLY_LITERAL_TYPES.has
Code
isIIFE(expression: TSESTree.Expression): expression is TSESTree.CallExpression & { callee: TSESTree.⦶
Parameters:
expressionTSESTree.Expression
Returns: expression is TSESTree.CallExpression & {
callee: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression;
}
Code
function isIIFE(
expression: TSESTree.Expression,
): expression is TSESTree.CallExpression & {
callee: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression;
} {
return (
expression.type === AST_NODE_TYPES.CallExpression &&
(expression.callee.type === AST_NODE_TYPES.ArrowFunctionExpression ||
expression.callee.type === AST_NODE_TYPES.FunctionExpression)
);
}
isEmptyObjectType(type: ts.Type): boolean¶
Parameters:
typets.Type
Returns: boolean
Calls:
isTypeFlagSet (from ../util)type.getPropertiestype.getCallSignaturestype.getConstructSignaturestype.getStringIndexTypetype.getNumberIndexType
Code
hasGenericCallSignature(type: ts.Type): boolean¶
Parameters:
typets.Type
Returns: boolean
Calls:
type.getCallSignatures().some
Code
isArgumentToOverloadedFunction(node: TSESTree.TSAsExpression | TSESTree.TSTyβ¦): boolean¶
Parameters:
nodeTSESTree.TSAsExpression | TSESTree.TSTypeAssertion
Returns: boolean
Calls:
parent.arguments.includesservices .getTypeAtLocation(parent.callee) .getNonNullableTypecalleeType.getCallSignaturesparent.arguments.indexOfsignatures.mapsig.getParameterschecker.getTypeOfSymbolts.isParametergetTypeArgumentsparamTypes.somedefinedParamTypes.everyservices.getTypeAtLocationchecker.isTypeAssignableTo
Internal Comments:
// An optional-chained callee (`foo?.bar(...)`) types as `<method> | undefined`, (x2)
// and a union exposes no call signatures β strip the nullability first. (x2)
Code
function isArgumentToOverloadedFunction(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
): boolean {
const { parent } = node;
if (
(parent.type !== AST_NODE_TYPES.CallExpression &&
parent.type !== AST_NODE_TYPES.NewExpression) ||
!parent.arguments.includes(node)
) {
return false;
}
// An optional-chained callee (`foo?.bar(...)`) types as `<method> | undefined`,
// and a union exposes no call signatures β strip the nullability first.
const calleeType = services
.getTypeAtLocation(parent.callee)
.getNonNullableType();
const signatures = calleeType.getCallSignatures();
if (signatures.length <= 1) {
return false;
}
const argIndex = parent.arguments.indexOf(node);
const paramTypes = signatures.map(sig => {
const params = sig.getParameters();
if (argIndex >= params.length) {
return undefined;
}
const param = params[argIndex];
let paramType = checker.getTypeOfSymbol(param);
if (
param.valueDeclaration &&
ts.isParameter(param.valueDeclaration) &&
param.valueDeclaration.dotDotDotToken
) {
const typeArgs = getTypeArguments(paramType);
if (typeArgs.length > 0) {
paramType = typeArgs[0];
}
}
return paramType;
});
if (paramTypes.some(type => type == null)) {
return true;
}
const definedParamTypes = paramTypes as ts.Type[];
const firstParamType = definedParamTypes[0];
if (definedParamTypes.every(type => type === firstParamType)) {
return false;
}
const uncastType = services.getTypeAtLocation(node.expression);
return !definedParamTypes.every(type =>
checker.isTypeAssignableTo(uncastType, type),
);
}
isInDestructuringDeclaration(node: TSESTree.TSAsExpression | TSESTree.TSTyβ¦): boolean¶
Parameters:
nodeTSESTree.TSAsExpression | TSESTree.TSTypeAssertion
Returns: boolean
Code
function isInDestructuringDeclaration(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
): boolean {
const { parent } = node;
return (
parent.type === AST_NODE_TYPES.VariableDeclarator &&
parent.init === node &&
(parent.id.type === AST_NODE_TYPES.ObjectPattern ||
parent.id.type === AST_NODE_TYPES.ArrayPattern)
);
}
isPropertyInProblematicContext(node: TSESTree.TSAsExpression | TSESTree.TSTyβ¦): boolean¶
Parameters:
nodeTSESTree.TSAsExpression | TSESTree.TSTypeAssertion
Returns: boolean
Calls:
services.esTreeNodeToTSNodeMap.getchecker.getContextualType(objectTsNode)?.isUnionchecker.getContextualTypechecker.getNonNullableTypenonNullableContextualType.isUnionservices.getTypeAtLocationchecker.isTypeAssignableTo
Code
function isPropertyInProblematicContext(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
): boolean {
const { parent } = node;
if (parent.type !== AST_NODE_TYPES.Property || parent.value !== node) {
return false;
}
const objectExpr = parent.parent;
if (objectExpr.type !== AST_NODE_TYPES.ObjectExpression) {
return false;
}
const objectTsNode = services.esTreeNodeToTSNodeMap.get(objectExpr);
if (checker.getContextualType(objectTsNode)?.isUnion()) {
const nodeTsNode = services.esTreeNodeToTSNodeMap.get(node);
const propContextualType = checker.getContextualType(nodeTsNode);
if (propContextualType == null) {
return true;
}
const nonNullableContextualType =
checker.getNonNullableType(propContextualType);
if (nonNullableContextualType.isUnion()) {
return true;
}
const uncastType = services.getTypeAtLocation(node.expression);
return !checker.isTypeAssignableTo(
uncastType,
nonNullableContextualType,
);
}
const objectParent = objectExpr.parent;
return (
objectParent.type === AST_NODE_TYPES.TSSatisfiesExpression ||
(objectParent.type === AST_NODE_TYPES.CallExpression &&
objectParent.parent.type === AST_NODE_TYPES.TSSatisfiesExpression)
);
}
isAssignmentInNonStatementContext(node: TSESTree.TSAsExpression | TSESTree.TSTyβ¦): boolean¶
Parameters:
nodeTSESTree.TSAsExpression | TSESTree.TSTypeAssertion
Returns: boolean
Code
function isAssignmentInNonStatementContext(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
): boolean {
const { parent } = node;
if (
parent.type !== AST_NODE_TYPES.AssignmentExpression ||
parent.right !== node
) {
return false;
}
const assignmentParent = parent.parent;
return assignmentParent.type !== AST_NODE_TYPES.ExpressionStatement;
}
isRightHandSideOfLogicalAssignment(node: TSESTree.TSAsExpression | TSESTree.TSTyβ¦): boolean¶
Parameters:
nodeTSESTree.TSAsExpression | TSESTree.TSTypeAssertion
Returns: boolean
Code
function isRightHandSideOfLogicalAssignment(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
): boolean {
const { parent } = node;
return (
parent.type === AST_NODE_TYPES.AssignmentExpression &&
parent.right === node &&
(parent.operator === '&&=' ||
parent.operator === '||=' ||
parent.operator === '??=')
);
}
isInGenericContext(node: TSESTree.TSAsExpression | TSESTree.TSTyβ¦): boolean¶
Parameters:
nodeTSESTree.TSAsExpression | TSESTree.TSTypeAssertion
Returns: boolean
Calls:
current.arguments.includeschecker.getTypeAtLocationservices.esTreeNodeToTSNodeMap.gethasGenericCallSignature
Code
function isInGenericContext(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
): boolean {
let seenFunction = false;
for (
let current: TSESTree.Node | undefined = node.parent;
current;
current = current.parent
) {
if (current.type === AST_NODE_TYPES.FunctionDeclaration) {
return false;
}
if (
current.type === AST_NODE_TYPES.FunctionExpression ||
current.type === AST_NODE_TYPES.ArrowFunctionExpression
) {
if (current.body.type === AST_NODE_TYPES.BlockStatement) {
return false;
}
if (seenFunction) {
return false;
}
seenFunction = true;
}
if (
current.type === AST_NODE_TYPES.CallExpression ||
current.type === AST_NODE_TYPES.NewExpression
) {
if (current.typeArguments != null) {
continue;
}
if (
current.type === AST_NODE_TYPES.CallExpression &&
current.callee.type === AST_NODE_TYPES.MemberExpression &&
current.arguments.includes(node)
) {
continue;
}
const calleeType = checker.getTypeAtLocation(
services.esTreeNodeToTSNodeMap.get(current.callee),
);
if (hasGenericCallSignature(calleeType)) {
return true;
}
}
}
return false;
}
hasPhantomTypeArgumentMismatch(node: TSESTree.TSAsExpression | TSESTree.TSTyβ¦, uncastType: ts.Type, contextualType: ts.Type): boolean¶
Parameters:
nodeTSESTree.TSAsExpression | TSESTree.TSTypeAssertionuncastTypets.TypecontextualTypets.Type
Returns: boolean
Calls:
isInGenericContexthasPhantomTypeArgumentshaveSameTypeArguments
Code
function hasPhantomTypeArgumentMismatch(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
uncastType: ts.Type,
contextualType: ts.Type,
): boolean {
return (
isInGenericContext(node) &&
(hasPhantomTypeArguments(uncastType) ||
hasPhantomTypeArguments(contextualType)) &&
!haveSameTypeArguments(uncastType, contextualType)
);
}
shouldSkipContextualTypeFallback(node: TSESTree.TSAsExpression | TSESTree.TSTyβ¦, castIsAny: boolean): boolean¶
Parameters:
nodeTSESTree.TSAsExpression | TSESTree.TSTypeAssertioncastIsAnyboolean
Returns: boolean
Calls:
isInGenericContextisTemplateLiteralWithExpressionsSKIP_PARENT_TYPES.hasisInDestructuringDeclarationisPropertyInProblematicContextisAssignmentInNonStatementContextisRightHandSideOfLogicalAssignmentisArgumentToOverloadedFunctiongetOriginalExpressionisConceptuallyLiteral
Internal Comments:
/**
* Interpolated template literals can be widened to `string` while contextual
* typing still accepts them, so the assertion may be required.
* @see https://github.com/typescript-eslint/typescript-eslint/issues/12276
*/
Code
function shouldSkipContextualTypeFallback(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
castIsAny: boolean,
): boolean {
if (castIsAny) {
return (
node.parent.type === AST_NODE_TYPES.LogicalExpression ||
isInGenericContext(node)
);
}
/**
* Interpolated template literals can be widened to `string` while contextual
* typing still accepts them, so the assertion may be required.
* @see https://github.com/typescript-eslint/typescript-eslint/issues/12276
*/
if (isTemplateLiteralWithExpressions(node.expression)) {
return true;
}
if (
SKIP_PARENT_TYPES.has(node.parent.type) ||
node.expression.type === AST_NODE_TYPES.ArrayExpression ||
isInDestructuringDeclaration(node) ||
isPropertyInProblematicContext(node) ||
isAssignmentInNonStatementContext(node) ||
isRightHandSideOfLogicalAssignment(node) ||
isArgumentToOverloadedFunction(node)
) {
return true;
}
if (isInGenericContext(node)) {
const originalExpr = getOriginalExpression(node);
return (
!isConceptuallyLiteral(originalExpr) &&
node.parent.type !== AST_NODE_TYPES.Property
);
}
return false;
}
getUncastType(node: TSESTree.TSAsExpression | TSESTree.TSTyβ¦): ts.Type¶
Parameters:
nodeTSESTree.TSAsExpression | TSESTree.TSTypeAssertion
Returns: ts.Type
Calls:
isIIFEservices.getTypeAtLocationfunctionType.getCallSignatureschecker.getReturnTypeOfSignatureisTypeFlagSet (from ../util)checker.getVoidType
Internal Comments:
// Special handling for IIFE: extract the function's return type
// If the function has no explicit return type annotation and returns undefined,
// treat it as void (TypeScript infers () => {} as () => undefined, but it should be void)
Code
function getUncastType(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
): ts.Type {
// Special handling for IIFE: extract the function's return type
if (isIIFE(node.expression)) {
const callee = node.expression.callee;
const functionType = services.getTypeAtLocation(callee);
const signatures = functionType.getCallSignatures();
if (signatures.length > 0) {
const returnType = checker.getReturnTypeOfSignature(signatures[0]);
// If the function has no explicit return type annotation and returns undefined,
// treat it as void (TypeScript infers () => {} as () => undefined, but it should be void)
if (
callee.returnType == null &&
isTypeFlagSet(returnType, ts.TypeFlags.Undefined)
) {
return checker.getVoidType();
}
return returnType;
}
}
return services.getTypeAtLocation(node.expression);
}
createAssertionFixer(node: TSESTree.TSAsExpression | TSESTree.TSTyβ¦): ReportFixFunction¶
Parameters:
nodeTSESTree.TSAsExpression | TSESTree.TSTypeAssertion
Returns: ReportFixFunction
Calls:
nullThrows (from ../util)context.sourceCode.getTokenBeforeNullThrowsReasons.MissingTokencontext.sourceCode.getTokenAfter['{', 'function', 'class'].includesisStartOfExpressionStatement (from ../util)isStartOfArrowFunctionBody (from ../util)fixes.pushfixer.insertTextBeforefixer.removeRangefixer.insertTextAfter
Internal Comments:
// Removing the angle brackets leaves the asserted operand at the (x2)
// assertion's position, so its first token leads whatever the (x2)
// assertion led. A leading `{`/`function`/`class` at the start of an (x2)
// expression statement is parsed as a block / function or class (x2)
// declaration, and a leading `{` at the start of a concise arrow body (x2)
// is parsed as a block body. In those positions the operand must be (x2)
// wrapped in parentheses to stay an expression. (x2)
Code
function createAssertionFixer(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
): ReportFixFunction {
return fixer => {
if (node.type === AST_NODE_TYPES.TSTypeAssertion) {
const openingAngleBracket = nullThrows(
context.sourceCode.getTokenBefore(
node.typeAnnotation,
token =>
token.type === AST_TOKEN_TYPES.Punctuator &&
token.value === '<',
),
NullThrowsReasons.MissingToken('<', 'type annotation'),
);
const closingAngleBracket = nullThrows(
context.sourceCode.getTokenAfter(
node.typeAnnotation,
token =>
token.type === AST_TOKEN_TYPES.Punctuator &&
token.value === '>',
),
NullThrowsReasons.MissingToken('>', 'type annotation'),
);
// Removing the angle brackets leaves the asserted operand at the
// assertion's position, so its first token leads whatever the
// assertion led. A leading `{`/`function`/`class` at the start of an
// expression statement is parsed as a block / function or class
// declaration, and a leading `{` at the start of a concise arrow body
// is parsed as a block body. In those positions the operand must be
// wrapped in parentheses to stay an expression.
const firstOperandToken = nullThrows(
context.sourceCode.getTokenAfter(closingAngleBracket),
NullThrowsReasons.MissingToken('operand', 'type assertion'),
);
const breaksExpressionStatement =
['{', 'function', 'class'].includes(firstOperandToken.value) &&
isStartOfExpressionStatement(node);
const breaksArrowFunctionBody =
firstOperandToken.value === '{' &&
isStartOfArrowFunctionBody(node, context.sourceCode);
const needsParens =
breaksExpressionStatement || breaksArrowFunctionBody;
const fixes: RuleFix[] = [];
if (needsParens) {
fixes.push(fixer.insertTextBefore(node, '('));
}
fixes.push(
fixer.removeRange([
openingAngleBracket.range[0],
closingAngleBracket.range[1],
]),
);
if (needsParens) {
fixes.push(fixer.insertTextAfter(node, ')'));
}
return fixes;
}
const asToken = nullThrows(
context.sourceCode.getTokenAfter(
node.expression,
token =>
token.type === AST_TOKEN_TYPES.Identifier && token.value === 'as',
),
NullThrowsReasons.MissingToken('>', 'type annotation'),
);
const tokenBeforeAs = nullThrows(
context.sourceCode.getTokenBefore(asToken, {
includeComments: true,
}),
NullThrowsReasons.MissingToken('comment', 'as'),
);
return fixer.removeRange([tokenBeforeAs.range[1], node.range[1]]);
};
}
reportDoubleAssertionIfUnnecessary(node: TSESTree.TSAsExpression | TSESTree.TSTyβ¦, contextualType: ts.Type | undefined): void¶
Parameters:
nodeTSESTree.TSAsExpression | TSESTree.TSTypeAssertioncontextualTypets.Type | undefined
Returns: void
Calls:
isDoubleAssertionUnnecessarycontext.reportgetOriginalExpressioncontext.sourceCode.getTextfixer.replaceText
Code
function reportDoubleAssertionIfUnnecessary(
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
contextualType: ts.Type | undefined,
): void {
const doubleAssertionResult = isDoubleAssertionUnnecessary(
node,
contextualType,
);
if (doubleAssertionResult) {
context.report({
node,
messageId: doubleAssertionResult,
fix(fixer) {
const originalExpr = getOriginalExpression(node);
let text = context.sourceCode.getText(originalExpr);
if (
originalExpr.type === AST_NODE_TYPES.ObjectExpression &&
node.parent.type === AST_NODE_TYPES.ArrowFunctionExpression &&
node.parent.body === node
) {
text = `(${text})`;
}
return fixer.replaceText(node, text);
},
});
}
}
removeExclamationFix(fixer: any): any¶
Parameters:
fixerany
Returns: any
Calls:
nullThrows (from ../util)context.sourceCode.getLastTokenNullThrowsReasons.MissingTokenfixer.removeRange
Code
Type Aliases¶
Options¶
MessageIds¶
Generated by Syntax Scribe