📄 no-unsafe-assignment¶
📊 Analysis Summary¶
| Metric | Count |
|---|---|
| 🔧 Functions | 8 |
| 📦 Imports | 13 |
| 🎯 Enums | 1 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 packages/eslint-plugin/src/rules/no-unsafe-assignment.ts
📤 Default Export¶
| Property | Value |
|---|---|
name |
'no-unsafe-assignment' |
meta.type |
'problem' |
meta.docs.description |
'Disallow assigning a value with type any to variables and properties' |
meta.docs.recommended |
'recommended' |
meta.docs.requiresTypeChecking |
true |
meta.messages.anyAssignment |
'Unsafe assignment of an {{sender}} value.' |
meta.messages.anyAssignmentThis |
[ 'Unsafe assignment of an {{sender}} value. this is typed as any.', 'You can try to fix this by turning on the `... |
meta.messages.unsafeArrayPattern |
'Unsafe array destructuring of an {{sender}} array value.' |
meta.messages.unsafeArrayPatternFromTuple |
'Unsafe array destructuring of a tuple element with an {{sender}} value.' |
meta.messages.unsafeArraySpread |
'Unsafe spread of an {{sender}} value in an array.' |
meta.messages.unsafeAssignment |
'Unsafe assignment of type {{sender}} to a variable of type {{receiver}}.' |
meta.messages.unsafeObjectPattern |
'Unsafe object destructuring of a property with an {{sender}} value.' |
meta.schema |
[] |
defaultOptions |
[] |
Entry point: create — documented under Functions.
📦 Imports¶
| Name | Source |
|---|---|
TSESTree |
@typescript-eslint/utils |
AST_NODE_TYPES |
@typescript-eslint/utils |
createRule |
../util |
getConstrainedTypeAtLocation |
../util |
getContextualType |
../util |
getParserServices |
../util |
getThisExpression |
../util |
isTypeAnyArrayType |
../util |
isTypeAnyType |
../util |
isTypeUnknownType |
../util |
isUnsafeAssignment |
../util |
nullThrows |
../util |
NullThrowsReasons |
../util |
Functions¶
create(context: any): { 'AccessorProperty[value != null]'(node: { value: object; …¶
Parameters:
contextany
Returns: { 'AccessorProperty[value != null]'(node: { value: object; } & TSESTree.AccessorProperty): void; 'AssignmentExpression[operator = "="], AssignmentPattern'(node: TSESTree.AssignmentExpression | TSESTree.AssignmentPattern): void; 'PropertyDefinition[value != null]'(node: { value: object; } & TSESTree.PropertyDefinition): void; 'VariableDeclarator[init != null]'(node: TSESTree.VariableDeclarator): void; ':not(ObjectPattern) > Property'(node: TSESTree.Property): void; 'ArrayExpression > SpreadElement'(node: TSESTree.SpreadElement): void; 'JSXAttribute[value != null]'(node: TSESTree.JSXAttribute): void; }
Calls:
getParserServices (from ../util)services.program.getTypeCheckerservices.program.getCompilerOptionstsutils.isStrictCompilerOptionEnabledservices.esTreeNodeToTSNodeMap.getservices.getTypeAtLocationcheckArrayDestructureisTypeAnyArrayType (from ../util)context.reportcreateDatachecker.isTupleTypechecker.getTypeArgumentsisTypeAnyType (from ../util)checkObjectDestructuresenderType .getProperties() .mapproperty.getNamechecker.getTypeOfSymbolAtLocationStringnullThrows (from ../util)properties.getgetContextualType (from ../util)isTypeUnknownType (from ../util)getThisExpression (from ../util)getConstrainedTypeAtLocation (from ../util)isUnsafeAssignment (from ../util)checker.typeToStringtsutils.isIntrinsicErrorTypecheckAssignmentgetComparisonTypecheckArrayDestructureHelpercheckObjectDestructureHelperNullThrowsReasons.MissingToken
Internal Comments:
// returns true if the assignment reported (x5)
// any array
// const [x] = ([] as any[]);
// tuple with any (x2)
// const [x] = [1 as any]; (x2)
// don't handle rests as they're not a 1:1 assignment
// check for the any type first so we can handle [[[x]]] = [any]
// we want to report on every invalid element in the tuple (x3)
// don't bother checking rest
// can't figure out the name, so skip it
// check for the any type first so we can handle {x: {y: z}} = {x: any}
// handle cases when we assign any ==> unknown.
// `var foo = this` (x2)
// the variable already has some form of a type to compare against (x2)
// object pattern props are checked via assignments (x2)
// handled by other selector
Code
create(context) {
const services = getParserServices(context);
const checker = services.program.getTypeChecker();
const compilerOptions = services.program.getCompilerOptions();
const isNoImplicitThis = tsutils.isStrictCompilerOptionEnabled(
compilerOptions,
'noImplicitThis',
);
// returns true if the assignment reported
function checkArrayDestructureHelper(
receiverNode: TSESTree.Node,
senderNode: TSESTree.Node,
): boolean {
if (receiverNode.type !== AST_NODE_TYPES.ArrayPattern) {
return false;
}
const senderTsNode = services.esTreeNodeToTSNodeMap.get(senderNode);
const senderType = services.getTypeAtLocation(senderNode);
return checkArrayDestructure(receiverNode, senderType, senderTsNode);
}
// returns true if the assignment reported
function checkArrayDestructure(
receiverNode: TSESTree.ArrayPattern,
senderType: ts.Type,
senderNode: ts.Node,
): boolean {
// any array
// const [x] = ([] as any[]);
if (isTypeAnyArrayType(senderType, checker)) {
context.report({
node: receiverNode,
messageId: 'unsafeArrayPattern',
data: createData(senderType),
});
return false;
}
if (!checker.isTupleType(senderType)) {
return true;
}
const tupleElements = checker.getTypeArguments(senderType);
// tuple with any
// const [x] = [1 as any];
let didReport = false;
for (
let receiverIndex = 0;
receiverIndex < receiverNode.elements.length;
receiverIndex += 1
) {
const receiverElement = receiverNode.elements[receiverIndex];
if (!receiverElement) {
continue;
}
if (receiverElement.type === AST_NODE_TYPES.RestElement) {
// don't handle rests as they're not a 1:1 assignment
continue;
}
const senderType = tupleElements[receiverIndex] as ts.Type | undefined;
if (!senderType) {
continue;
}
// check for the any type first so we can handle [[[x]]] = [any]
if (isTypeAnyType(senderType)) {
context.report({
node: receiverElement,
messageId: 'unsafeArrayPatternFromTuple',
data: createData(senderType),
});
// we want to report on every invalid element in the tuple
didReport = true;
} else if (receiverElement.type === AST_NODE_TYPES.ArrayPattern) {
didReport = checkArrayDestructure(
receiverElement,
senderType,
senderNode,
);
} else if (receiverElement.type === AST_NODE_TYPES.ObjectPattern) {
didReport = checkObjectDestructure(
receiverElement,
senderType,
senderNode,
);
}
}
return didReport;
}
// returns true if the assignment reported
function checkObjectDestructureHelper(
receiverNode: TSESTree.Node,
senderNode: TSESTree.Node,
): boolean {
if (receiverNode.type !== AST_NODE_TYPES.ObjectPattern) {
return false;
}
const senderTsNode = services.esTreeNodeToTSNodeMap.get(senderNode);
const senderType = services.getTypeAtLocation(senderNode);
return checkObjectDestructure(receiverNode, senderType, senderTsNode);
}
// returns true if the assignment reported
function checkObjectDestructure(
receiverNode: TSESTree.ObjectPattern,
senderType: ts.Type,
senderNode: ts.Node,
): boolean {
const properties = new Map(
senderType
.getProperties()
.map(property => [
property.getName(),
checker.getTypeOfSymbolAtLocation(property, senderNode),
]),
);
let didReport = false;
for (const receiverProperty of receiverNode.properties) {
if (receiverProperty.type === AST_NODE_TYPES.RestElement) {
// don't bother checking rest
continue;
}
let key: string;
if (!receiverProperty.computed) {
key =
receiverProperty.key.type === AST_NODE_TYPES.Identifier
? receiverProperty.key.name
: String(receiverProperty.key.value);
} else if (receiverProperty.key.type === AST_NODE_TYPES.Literal) {
key = String(receiverProperty.key.value);
} else if (
receiverProperty.key.type === AST_NODE_TYPES.TemplateLiteral &&
receiverProperty.key.quasis.length === 1
) {
const cooked = nullThrows(
receiverProperty.key.quasis[0].value.cooked,
'cooked can only be null inside a TaggedTemplateExpression, which is not possible here',
);
key = cooked;
} else {
// can't figure out the name, so skip it
continue;
}
const senderType = properties.get(key);
if (!senderType) {
continue;
}
// check for the any type first so we can handle {x: {y: z}} = {x: any}
if (isTypeAnyType(senderType)) {
context.report({
node: receiverProperty.value,
messageId: 'unsafeObjectPattern',
data: createData(senderType),
});
didReport = true;
} else if (
receiverProperty.value.type === AST_NODE_TYPES.ArrayPattern
) {
didReport = checkArrayDestructure(
receiverProperty.value,
senderType,
senderNode,
);
} else if (
receiverProperty.value.type === AST_NODE_TYPES.ObjectPattern
) {
didReport = checkObjectDestructure(
receiverProperty.value,
senderType,
senderNode,
);
}
}
return didReport;
}
// returns true if the assignment reported
function checkAssignment(
receiverNode: TSESTree.Node,
senderNode: TSESTree.Expression,
reportingNode: TSESTree.Node,
comparisonType: ComparisonType,
): boolean {
const receiverTsNode = services.esTreeNodeToTSNodeMap.get(receiverNode);
const receiverType =
comparisonType === ComparisonType.Contextual
? (getContextualType(checker, receiverTsNode as ts.Expression) ??
services.getTypeAtLocation(receiverNode))
: services.getTypeAtLocation(receiverNode);
const senderType = services.getTypeAtLocation(senderNode);
if (isTypeAnyType(senderType)) {
// handle cases when we assign any ==> unknown.
if (isTypeUnknownType(receiverType)) {
return false;
}
let messageId: 'anyAssignment' | 'anyAssignmentThis' = 'anyAssignment';
if (!isNoImplicitThis) {
// `var foo = this`
const thisExpression = getThisExpression(senderNode);
if (
thisExpression &&
isTypeAnyType(
getConstrainedTypeAtLocation(services, thisExpression),
)
) {
messageId = 'anyAssignmentThis';
}
}
context.report({
node: reportingNode,
messageId,
data: createData(senderType),
});
return true;
}
if (comparisonType === ComparisonType.None) {
return false;
}
const result = isUnsafeAssignment(
senderType,
receiverType,
checker,
senderNode,
);
if (!result) {
return false;
}
const { receiver, sender } = result;
context.report({
node: reportingNode,
messageId: 'unsafeAssignment',
data: createData(sender, receiver),
});
return true;
}
function getComparisonType(
typeAnnotation: TSESTree.TSTypeAnnotation | undefined,
): ComparisonType {
return typeAnnotation
? // if there's a type annotation, we can do a comparison
ComparisonType.Basic
: // no type annotation means the variable's type will just be inferred, thus equal
ComparisonType.None;
}
function createData(
senderType: ts.Type,
receiverType?: ts.Type,
): Readonly<Record<string, unknown>> | undefined {
if (receiverType) {
return {
receiver: `\`${checker.typeToString(receiverType)}\``,
sender: `\`${checker.typeToString(senderType)}\``,
};
}
return {
sender: tsutils.isIntrinsicErrorType(senderType)
? 'error typed'
: '`any`',
};
}
return {
'AccessorProperty[value != null]'(
node: { value: object } & TSESTree.AccessorProperty,
): void {
checkAssignment(
node.key,
node.value,
node,
getComparisonType(node.typeAnnotation),
);
},
'AssignmentExpression[operator = "="], AssignmentPattern'(
node: TSESTree.AssignmentExpression | TSESTree.AssignmentPattern,
): void {
let didReport = checkAssignment(
node.left,
node.right,
node,
// the variable already has some form of a type to compare against
ComparisonType.Basic,
);
if (!didReport) {
didReport = checkArrayDestructureHelper(node.left, node.right);
}
if (!didReport) {
checkObjectDestructureHelper(node.left, node.right);
}
},
'PropertyDefinition[value != null]'(
node: { value: object } & TSESTree.PropertyDefinition,
): void {
checkAssignment(
node.key,
node.value,
node,
getComparisonType(node.typeAnnotation),
);
},
'VariableDeclarator[init != null]'(
node: TSESTree.VariableDeclarator,
): void {
const init = nullThrows(
node.init,
NullThrowsReasons.MissingToken(node.type, 'init'),
);
let didReport = checkAssignment(
node.id,
init,
node,
getComparisonType(node.id.typeAnnotation),
);
if (!didReport) {
didReport = checkArrayDestructureHelper(node.id, init);
}
if (!didReport) {
checkObjectDestructureHelper(node.id, init);
}
},
// object pattern props are checked via assignments
':not(ObjectPattern) > Property'(node: TSESTree.Property): void {
if (
node.value.type === AST_NODE_TYPES.AssignmentPattern ||
node.value.type === AST_NODE_TYPES.TSEmptyBodyFunctionExpression
) {
// handled by other selector
return;
}
checkAssignment(node.key, node.value, node, ComparisonType.Contextual);
},
'ArrayExpression > SpreadElement'(node: TSESTree.SpreadElement): void {
const restType = services.getTypeAtLocation(node.argument);
if (isTypeAnyType(restType) || isTypeAnyArrayType(restType, checker)) {
context.report({
node,
messageId: 'unsafeArraySpread',
data: createData(restType),
});
}
},
'JSXAttribute[value != null]'(node: TSESTree.JSXAttribute): void {
const value = nullThrows(
node.value,
NullThrowsReasons.MissingToken(node.type, 'value'),
);
if (
value.type !== AST_NODE_TYPES.JSXExpressionContainer ||
value.expression.type === AST_NODE_TYPES.JSXEmptyExpression
) {
return;
}
checkAssignment(
node.name,
value.expression,
value.expression,
ComparisonType.Contextual,
);
},
};
}
Internal helpers¶
Declared inside another function in this file.
checkArrayDestructureHelper(receiverNode: TSESTree.Node, senderNode: TSESTree.Node): boolean¶
Parameters:
receiverNodeTSESTree.NodesenderNodeTSESTree.Node
Returns: boolean
Calls:
services.esTreeNodeToTSNodeMap.getservices.getTypeAtLocationcheckArrayDestructure
Code
function checkArrayDestructureHelper(
receiverNode: TSESTree.Node,
senderNode: TSESTree.Node,
): boolean {
if (receiverNode.type !== AST_NODE_TYPES.ArrayPattern) {
return false;
}
const senderTsNode = services.esTreeNodeToTSNodeMap.get(senderNode);
const senderType = services.getTypeAtLocation(senderNode);
return checkArrayDestructure(receiverNode, senderType, senderTsNode);
}
checkArrayDestructure(receiverNode: TSESTree.ArrayPattern, senderType: ts.Type, senderNode: ts.Node): boolean¶
Parameters:
receiverNodeTSESTree.ArrayPatternsenderTypets.TypesenderNodets.Node
Returns: boolean
Calls:
isTypeAnyArrayType (from ../util)context.reportcreateDatachecker.isTupleTypechecker.getTypeArgumentsisTypeAnyType (from ../util)checkArrayDestructurecheckObjectDestructure
Internal Comments:
// any array
// const [x] = ([] as any[]);
// tuple with any (x2)
// const [x] = [1 as any]; (x2)
// don't handle rests as they're not a 1:1 assignment
// check for the any type first so we can handle [[[x]]] = [any]
// we want to report on every invalid element in the tuple (x3)
Code
function checkArrayDestructure(
receiverNode: TSESTree.ArrayPattern,
senderType: ts.Type,
senderNode: ts.Node,
): boolean {
// any array
// const [x] = ([] as any[]);
if (isTypeAnyArrayType(senderType, checker)) {
context.report({
node: receiverNode,
messageId: 'unsafeArrayPattern',
data: createData(senderType),
});
return false;
}
if (!checker.isTupleType(senderType)) {
return true;
}
const tupleElements = checker.getTypeArguments(senderType);
// tuple with any
// const [x] = [1 as any];
let didReport = false;
for (
let receiverIndex = 0;
receiverIndex < receiverNode.elements.length;
receiverIndex += 1
) {
const receiverElement = receiverNode.elements[receiverIndex];
if (!receiverElement) {
continue;
}
if (receiverElement.type === AST_NODE_TYPES.RestElement) {
// don't handle rests as they're not a 1:1 assignment
continue;
}
const senderType = tupleElements[receiverIndex] as ts.Type | undefined;
if (!senderType) {
continue;
}
// check for the any type first so we can handle [[[x]]] = [any]
if (isTypeAnyType(senderType)) {
context.report({
node: receiverElement,
messageId: 'unsafeArrayPatternFromTuple',
data: createData(senderType),
});
// we want to report on every invalid element in the tuple
didReport = true;
} else if (receiverElement.type === AST_NODE_TYPES.ArrayPattern) {
didReport = checkArrayDestructure(
receiverElement,
senderType,
senderNode,
);
} else if (receiverElement.type === AST_NODE_TYPES.ObjectPattern) {
didReport = checkObjectDestructure(
receiverElement,
senderType,
senderNode,
);
}
}
return didReport;
}
checkObjectDestructureHelper(receiverNode: TSESTree.Node, senderNode: TSESTree.Node): boolean¶
Parameters:
receiverNodeTSESTree.NodesenderNodeTSESTree.Node
Returns: boolean
Calls:
services.esTreeNodeToTSNodeMap.getservices.getTypeAtLocationcheckObjectDestructure
Code
function checkObjectDestructureHelper(
receiverNode: TSESTree.Node,
senderNode: TSESTree.Node,
): boolean {
if (receiverNode.type !== AST_NODE_TYPES.ObjectPattern) {
return false;
}
const senderTsNode = services.esTreeNodeToTSNodeMap.get(senderNode);
const senderType = services.getTypeAtLocation(senderNode);
return checkObjectDestructure(receiverNode, senderType, senderTsNode);
}
checkObjectDestructure(receiverNode: TSESTree.ObjectPattern, senderType: ts.Type, senderNode: ts.Node): boolean¶
Parameters:
receiverNodeTSESTree.ObjectPatternsenderTypets.TypesenderNodets.Node
Returns: boolean
Calls:
senderType .getProperties() .mapproperty.getNamechecker.getTypeOfSymbolAtLocationStringnullThrows (from ../util)properties.getisTypeAnyType (from ../util)context.reportcreateDatacheckArrayDestructurecheckObjectDestructure
Internal Comments:
// don't bother checking rest
// can't figure out the name, so skip it
// check for the any type first so we can handle {x: {y: z}} = {x: any}
Code
function checkObjectDestructure(
receiverNode: TSESTree.ObjectPattern,
senderType: ts.Type,
senderNode: ts.Node,
): boolean {
const properties = new Map(
senderType
.getProperties()
.map(property => [
property.getName(),
checker.getTypeOfSymbolAtLocation(property, senderNode),
]),
);
let didReport = false;
for (const receiverProperty of receiverNode.properties) {
if (receiverProperty.type === AST_NODE_TYPES.RestElement) {
// don't bother checking rest
continue;
}
let key: string;
if (!receiverProperty.computed) {
key =
receiverProperty.key.type === AST_NODE_TYPES.Identifier
? receiverProperty.key.name
: String(receiverProperty.key.value);
} else if (receiverProperty.key.type === AST_NODE_TYPES.Literal) {
key = String(receiverProperty.key.value);
} else if (
receiverProperty.key.type === AST_NODE_TYPES.TemplateLiteral &&
receiverProperty.key.quasis.length === 1
) {
const cooked = nullThrows(
receiverProperty.key.quasis[0].value.cooked,
'cooked can only be null inside a TaggedTemplateExpression, which is not possible here',
);
key = cooked;
} else {
// can't figure out the name, so skip it
continue;
}
const senderType = properties.get(key);
if (!senderType) {
continue;
}
// check for the any type first so we can handle {x: {y: z}} = {x: any}
if (isTypeAnyType(senderType)) {
context.report({
node: receiverProperty.value,
messageId: 'unsafeObjectPattern',
data: createData(senderType),
});
didReport = true;
} else if (
receiverProperty.value.type === AST_NODE_TYPES.ArrayPattern
) {
didReport = checkArrayDestructure(
receiverProperty.value,
senderType,
senderNode,
);
} else if (
receiverProperty.value.type === AST_NODE_TYPES.ObjectPattern
) {
didReport = checkObjectDestructure(
receiverProperty.value,
senderType,
senderNode,
);
}
}
return didReport;
}
checkAssignment(receiverNode: TSESTree.Node, senderNode: TSESTree.Expression, reportingNode: TSESTree.Node, comparisonType: ComparisonType): boolean¶
Parameters:
receiverNodeTSESTree.NodesenderNodeTSESTree.ExpressionreportingNodeTSESTree.NodecomparisonTypeComparisonType
Returns: boolean
Calls:
services.esTreeNodeToTSNodeMap.getgetContextualType (from ../util)services.getTypeAtLocationisTypeAnyType (from ../util)isTypeUnknownType (from ../util)getThisExpression (from ../util)getConstrainedTypeAtLocation (from ../util)context.reportcreateDataisUnsafeAssignment (from ../util)
Internal Comments:
Code
function checkAssignment(
receiverNode: TSESTree.Node,
senderNode: TSESTree.Expression,
reportingNode: TSESTree.Node,
comparisonType: ComparisonType,
): boolean {
const receiverTsNode = services.esTreeNodeToTSNodeMap.get(receiverNode);
const receiverType =
comparisonType === ComparisonType.Contextual
? (getContextualType(checker, receiverTsNode as ts.Expression) ??
services.getTypeAtLocation(receiverNode))
: services.getTypeAtLocation(receiverNode);
const senderType = services.getTypeAtLocation(senderNode);
if (isTypeAnyType(senderType)) {
// handle cases when we assign any ==> unknown.
if (isTypeUnknownType(receiverType)) {
return false;
}
let messageId: 'anyAssignment' | 'anyAssignmentThis' = 'anyAssignment';
if (!isNoImplicitThis) {
// `var foo = this`
const thisExpression = getThisExpression(senderNode);
if (
thisExpression &&
isTypeAnyType(
getConstrainedTypeAtLocation(services, thisExpression),
)
) {
messageId = 'anyAssignmentThis';
}
}
context.report({
node: reportingNode,
messageId,
data: createData(senderType),
});
return true;
}
if (comparisonType === ComparisonType.None) {
return false;
}
const result = isUnsafeAssignment(
senderType,
receiverType,
checker,
senderNode,
);
if (!result) {
return false;
}
const { receiver, sender } = result;
context.report({
node: reportingNode,
messageId: 'unsafeAssignment',
data: createData(sender, receiver),
});
return true;
}
getComparisonType(typeAnnotation: TSESTree.TSTypeAnnotation | undefined): ComparisonType¶
Parameters:
typeAnnotationTSESTree.TSTypeAnnotation | undefined
Returns: ComparisonType
Code
function getComparisonType(
typeAnnotation: TSESTree.TSTypeAnnotation | undefined,
): ComparisonType {
return typeAnnotation
? // if there's a type annotation, we can do a comparison
ComparisonType.Basic
: // no type annotation means the variable's type will just be inferred, thus equal
ComparisonType.None;
}
createData(senderType: ts.Type, receiverType: ts.Type): Readonly<Record<string, unknown>> | undefined¶
Parameters:
senderTypets.TypereceiverTypets.Type
Returns: Readonly<Record<string, unknown>> | undefined
Calls:
checker.typeToStringtsutils.isIntrinsicErrorType
Code
function createData(
senderType: ts.Type,
receiverType?: ts.Type,
): Readonly<Record<string, unknown>> | undefined {
if (receiverType) {
return {
receiver: `\`${checker.typeToString(receiverType)}\``,
sender: `\`${checker.typeToString(senderType)}\``,
};
}
return {
sender: tsutils.isIntrinsicErrorType(senderType)
? 'error typed'
: '`any`',
};
}
Enums¶
const enum ComparisonType¶
Enum Code
Members¶
| Name | Value | Description |
|---|---|---|
None |
auto | / Do no assignment comparison */ |
Basic |
auto | / Use the receiver's type for comparison */ |
Contextual |
auto | / Use the sender's contextual type for comparison */ |
Generated by Syntax Scribe