diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7b4fd5803e3..b105721ba13 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5777,7 +5777,7 @@ namespace ts { getIndexInfoOfType(objectType, IndexKind.String) || undefined; if (indexInfo) { - if (accessExpression && isAssignmentTarget(accessExpression) && indexInfo.isReadonly) { + if (accessExpression && indexInfo.isReadonly && (isAssignmentTarget(accessExpression) || isDeleteTarget(accessExpression))) { error(accessExpression, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType)); return unknownType; } @@ -13550,9 +13550,16 @@ namespace ts { function checkDeleteExpression(node: DeleteExpression): Type { checkExpression(node.expression); - checkReferenceExpression(node.expression, - Diagnostics.The_operand_of_a_delete_operator_must_be_a_property_reference, - Diagnostics.The_operand_of_a_delete_operator_cannot_be_a_read_only_property); + const expr = skipParentheses(node.expression); + if (expr.kind !== SyntaxKind.PropertyAccessExpression && expr.kind !== SyntaxKind.ElementAccessExpression) { + error(expr, Diagnostics.The_operand_of_a_delete_operator_must_be_a_property_reference); + return booleanType; + } + const links = getNodeLinks(expr); + const symbol = getExportSymbolOfValueSymbolIfExported(links.resolvedSymbol); + if (symbol && isReadonlySymbol(symbol)) { + error(expr, Diagnostics.The_operand_of_a_delete_operator_cannot_be_a_read_only_property); + } return booleanType; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index e4c92c502a4..9e1b35c60da 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2651,7 +2651,6 @@ namespace ts { resolvedType?: Type; // Cached type of type node resolvedSignature?: Signature; // Cached signature of signature node or call expression resolvedSymbol?: Symbol; // Cached name resolution result - resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result enumMemberValue?: number; // Constant value of enum member isVisible?: boolean; // Is this node visible hasReportedStatementInAmbientContext?: boolean; // Cache boolean if we report statements in ambient context diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 299f91be662..8e7b11ccf91 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1,4 +1,4 @@ -/// +/// /* @internal */ namespace ts { @@ -1666,6 +1666,18 @@ namespace ts { return getAssignmentTargetKind(node) !== AssignmentKind.None; } + // a node is delete target iff. it is PropertyAccessExpression/ElementAccessExpression with parentheses skipped + export function isDeleteTarget(node: Node): boolean { + if (node.kind !== SyntaxKind.PropertyAccessExpression && node.kind !== SyntaxKind.ElementAccessExpression) { + return false; + } + node = node.parent; + while (node && node.kind === SyntaxKind.ParenthesizedExpression) { + node = node.parent; + } + return node && node.kind === SyntaxKind.DeleteExpression; + } + export function isNodeDescendantOf(node: Node, ancestor: Node): boolean { while (node) { if (node === ancestor) return true;