Skip to content

Commit e8b72aa

Browse files
author
Andy
authored
Error on accessing private property through destructuring assignment, and mark property used (#26381)
* Error on accessing private property through destructuring assignment, and mark property used * Factor out getTypeOfObjectLiteralDestructuringProperty
1 parent ec1cc0a commit e8b72aa

11 files changed

+316
-39
lines changed

src/compiler/checker.ts

+46-39
Original file line numberDiff line numberDiff line change
@@ -17736,25 +17736,23 @@ namespace ts {
1773617736
* Check whether the requested property access is valid.
1773717737
* Returns true if node is a valid property access, and false otherwise.
1773817738
* @param node The node to be checked.
17739-
* @param left The left hand side of the property access (e.g.: the super in `super.foo`).
17740-
* @param type The type of left.
17741-
* @param prop The symbol for the right hand side of the property access.
17739+
* @param isSuper True if the access is from `super.`.
17740+
* @param type The type of the object whose property is being accessed. (Not the type of the property.)
17741+
* @param prop The symbol for the property being accessed.
1774217742
*/
17743-
function checkPropertyAccessibility(node: PropertyAccessExpression | QualifiedName | VariableLikeDeclaration | ImportTypeNode, left: Expression | QualifiedName | ImportTypeNode, type: Type, prop: Symbol): boolean {
17743+
function checkPropertyAccessibility(
17744+
node: PropertyAccessExpression | QualifiedName | PropertyAccessExpression | VariableDeclaration | ParameterDeclaration | ImportTypeNode | PropertyAssignment | ShorthandPropertyAssignment | BindingElement,
17745+
isSuper: boolean, type: Type, prop: Symbol): boolean {
1774417746
const flags = getDeclarationModifierFlagsFromSymbol(prop);
17745-
const errorNode = node.kind === SyntaxKind.PropertyAccessExpression || node.kind === SyntaxKind.VariableDeclaration ?
17746-
node.name :
17747-
node.kind === SyntaxKind.ImportType ?
17748-
node :
17749-
(<QualifiedName>node).right;
17747+
const errorNode = node.kind === SyntaxKind.QualifiedName ? node.right : node.kind === SyntaxKind.ImportType ? node : node.name;
1775017748

1775117749
if (getCheckFlags(prop) & CheckFlags.ContainsPrivate) {
1775217750
// Synthetic property with private constituent property
1775317751
error(errorNode, Diagnostics.Property_0_has_conflicting_declarations_and_is_inaccessible_in_type_1, symbolToString(prop), typeToString(type));
1775417752
return false;
1775517753
}
1775617754

17757-
if (left.kind === SyntaxKind.SuperKeyword) {
17755+
if (isSuper) {
1775817756
// TS 1.0 spec (April 2014): 4.8.2
1775917757
// - In a constructor, instance member function, instance member accessor, or
1776017758
// instance member variable initializer where this references a derived class instance,
@@ -17807,7 +17805,7 @@ namespace ts {
1780717805
// Property is known to be protected at this point
1780817806

1780917807
// All protected properties of a supertype are accessible in a super access
17810-
if (left.kind === SyntaxKind.SuperKeyword) {
17808+
if (isSuper) {
1781117809
return true;
1781217810
}
1781317811

@@ -17940,7 +17938,7 @@ namespace ts {
1794017938
checkPropertyNotUsedBeforeDeclaration(prop, node, right);
1794117939
markPropertyAsReferenced(prop, node, left.kind === SyntaxKind.ThisKeyword);
1794217940
getNodeLinks(node).resolvedSymbol = prop;
17943-
checkPropertyAccessibility(node, left, apparentType, prop);
17941+
checkPropertyAccessibility(node, left.kind === SyntaxKind.SuperKeyword, apparentType, prop);
1794417942
if (assignmentKind) {
1794517943
if (isReferenceToReadonlyEntity(<Expression>node, prop) || isReferenceThroughNamespaceImport(<Expression>node)) {
1794617944
error(right, Diagnostics.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property, idText(right));
@@ -18174,16 +18172,16 @@ namespace ts {
1817418172
function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName | ImportTypeNode, propertyName: __String): boolean {
1817518173
switch (node.kind) {
1817618174
case SyntaxKind.PropertyAccessExpression:
18177-
return isValidPropertyAccessWithType(node, node.expression, propertyName, getWidenedType(checkExpression(node.expression)));
18175+
return isValidPropertyAccessWithType(node, node.expression.kind === SyntaxKind.SuperKeyword, propertyName, getWidenedType(checkExpression(node.expression)));
1817818176
case SyntaxKind.QualifiedName:
18179-
return isValidPropertyAccessWithType(node, node.left, propertyName, getWidenedType(checkExpression(node.left)));
18177+
return isValidPropertyAccessWithType(node, /*isSuper*/ false, propertyName, getWidenedType(checkExpression(node.left)));
1818018178
case SyntaxKind.ImportType:
18181-
return isValidPropertyAccessWithType(node, node, propertyName, getTypeFromTypeNode(node));
18179+
return isValidPropertyAccessWithType(node, /*isSuper*/ false, propertyName, getTypeFromTypeNode(node));
1818218180
}
1818318181
}
1818418182

1818518183
function isValidPropertyAccessForCompletions(node: PropertyAccessExpression | ImportTypeNode, type: Type, property: Symbol): boolean {
18186-
return isValidPropertyAccessWithType(node, node.kind === SyntaxKind.ImportType ? node : node.expression, property.escapedName, type)
18184+
return isValidPropertyAccessWithType(node, node.kind !== SyntaxKind.ImportType && node.expression.kind === SyntaxKind.SuperKeyword, property.escapedName, type)
1818718185
&& (!(property.flags & SymbolFlags.Method) || isValidMethodAccess(property, type));
1818818186
}
1818918187
function isValidMethodAccess(method: Symbol, actualThisType: Type): boolean {
@@ -18206,17 +18204,17 @@ namespace ts {
1820618204

1820718205
function isValidPropertyAccessWithType(
1820818206
node: PropertyAccessExpression | QualifiedName | ImportTypeNode,
18209-
left: LeftHandSideExpression | QualifiedName | ImportTypeNode,
18207+
isSuper: boolean,
1821018208
propertyName: __String,
1821118209
type: Type): boolean {
1821218210

1821318211
if (type === errorType || isTypeAny(type)) {
1821418212
return true;
1821518213
}
1821618214
const prop = getPropertyOfType(type, propertyName);
18217-
return prop ? checkPropertyAccessibility(node, left, type, prop)
18215+
return prop ? checkPropertyAccessibility(node, isSuper, type, prop)
1821818216
// In js files properties of unions are allowed in completion
18219-
: isInJavaScriptFile(node) && (type.flags & TypeFlags.Union) !== 0 && (<UnionType>type).types.some(elementType => isValidPropertyAccessWithType(node, left, propertyName, elementType));
18217+
: isInJavaScriptFile(node) && (type.flags & TypeFlags.Union) !== 0 && (<UnionType>type).types.some(elementType => isValidPropertyAccessWithType(node, isSuper, propertyName, elementType));
1822018218
}
1822118219

1822218220
/**
@@ -21093,19 +21091,19 @@ namespace ts {
2109321091
return booleanType;
2109421092
}
2109521093

21096-
function checkObjectLiteralAssignment(node: ObjectLiteralExpression, sourceType: Type): Type {
21094+
function checkObjectLiteralAssignment(node: ObjectLiteralExpression, sourceType: Type, rightIsThis?: boolean): Type {
2109721095
const properties = node.properties;
2109821096
if (strictNullChecks && properties.length === 0) {
2109921097
return checkNonNullType(sourceType, node);
2110021098
}
2110121099
for (const p of properties) {
21102-
checkObjectLiteralDestructuringPropertyAssignment(sourceType, p, properties);
21100+
checkObjectLiteralDestructuringPropertyAssignment(sourceType, p, properties, rightIsThis);
2110321101
}
2110421102
return sourceType;
2110521103
}
2110621104

2110721105
/** Note: If property cannot be a SpreadAssignment, then allProperties does not need to be provided */
21108-
function checkObjectLiteralDestructuringPropertyAssignment(objectLiteralType: Type, property: ObjectLiteralElementLike, allProperties?: NodeArray<ObjectLiteralElementLike>) {
21106+
function checkObjectLiteralDestructuringPropertyAssignment(objectLiteralType: Type, property: ObjectLiteralElementLike, allProperties?: NodeArray<ObjectLiteralElementLike>, rightIsThis = false) {
2110921107
if (property.kind === SyntaxKind.PropertyAssignment || property.kind === SyntaxKind.ShorthandPropertyAssignment) {
2111021108
const name = property.name;
2111121109
if (name.kind === SyntaxKind.ComputedPropertyName) {
@@ -21115,20 +21113,10 @@ namespace ts {
2111521113
return undefined;
2111621114
}
2111721115

21118-
const text = getTextOfPropertyName(name);
21119-
const type = isTypeAny(objectLiteralType)
21120-
? objectLiteralType
21121-
: getTypeOfPropertyOfType(objectLiteralType, text) ||
21122-
isNumericLiteralName(text) && getIndexTypeOfType(objectLiteralType, IndexKind.Number) ||
21123-
getIndexTypeOfType(objectLiteralType, IndexKind.String);
21116+
const type = getTypeOfObjectLiteralDestructuringProperty(objectLiteralType, name, property, rightIsThis);
2112421117
if (type) {
21125-
if (property.kind === SyntaxKind.ShorthandPropertyAssignment) {
21126-
return checkDestructuringAssignment(property, type);
21127-
}
21128-
else {
21129-
// non-shorthand property assignments should always have initializers
21130-
return checkDestructuringAssignment(property.initializer, type);
21131-
}
21118+
// non-shorthand property assignments should always have initializers
21119+
return checkDestructuringAssignment(property.kind === SyntaxKind.ShorthandPropertyAssignment ? property : property.initializer, type);
2113221120
}
2113321121
else {
2113421122
error(name, Diagnostics.Type_0_has_no_property_1_and_no_string_index_signature, typeToString(objectLiteralType), declarationNameToString(name));
@@ -21153,6 +21141,25 @@ namespace ts {
2115321141
}
2115421142
}
2115521143

21144+
function getTypeOfObjectLiteralDestructuringProperty(objectLiteralType: Type, name: PropertyName, property: PropertyAssignment | ShorthandPropertyAssignment, rightIsThis: boolean) {
21145+
if (isTypeAny(objectLiteralType)) {
21146+
return objectLiteralType;
21147+
}
21148+
21149+
let type: Type | undefined;
21150+
const text = getTextOfPropertyName(name);
21151+
if (text) { // TODO: GH#26379
21152+
const prop = getPropertyOfType(objectLiteralType, text);
21153+
if (prop) {
21154+
markPropertyAsReferenced(prop, property, rightIsThis);
21155+
checkPropertyAccessibility(property, /*isSuper*/ false, objectLiteralType, prop);
21156+
type = getTypeOfSymbol(prop);
21157+
}
21158+
type = type || (isNumericLiteralName(text) ? getIndexTypeOfType(objectLiteralType, IndexKind.Number) : undefined);
21159+
}
21160+
return type || getIndexTypeOfType(objectLiteralType, IndexKind.String);
21161+
}
21162+
2115621163
function checkArrayLiteralAssignment(node: ArrayLiteralExpression, sourceType: Type, checkMode?: CheckMode): Type {
2115721164
const elements = node.elements;
2115821165
if (languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) {
@@ -21214,7 +21221,7 @@ namespace ts {
2121421221
return undefined;
2121521222
}
2121621223

21217-
function checkDestructuringAssignment(exprOrAssignment: Expression | ShorthandPropertyAssignment, sourceType: Type, checkMode?: CheckMode): Type {
21224+
function checkDestructuringAssignment(exprOrAssignment: Expression | ShorthandPropertyAssignment, sourceType: Type, checkMode?: CheckMode, rightIsThis?: boolean): Type {
2121821225
let target: Expression;
2121921226
if (exprOrAssignment.kind === SyntaxKind.ShorthandPropertyAssignment) {
2122021227
const prop = <ShorthandPropertyAssignment>exprOrAssignment;
@@ -21238,7 +21245,7 @@ namespace ts {
2123821245
target = (<BinaryExpression>target).left;
2123921246
}
2124021247
if (target.kind === SyntaxKind.ObjectLiteralExpression) {
21241-
return checkObjectLiteralAssignment(<ObjectLiteralExpression>target, sourceType);
21248+
return checkObjectLiteralAssignment(<ObjectLiteralExpression>target, sourceType, rightIsThis);
2124221249
}
2124321250
if (target.kind === SyntaxKind.ArrayLiteralExpression) {
2124421251
return checkArrayLiteralAssignment(<ArrayLiteralExpression>target, sourceType, checkMode);
@@ -21337,7 +21344,7 @@ namespace ts {
2133721344
function checkBinaryLikeExpression(left: Expression, operatorToken: Node, right: Expression, checkMode?: CheckMode, errorNode?: Node): Type {
2133821345
const operator = operatorToken.kind;
2133921346
if (operator === SyntaxKind.EqualsToken && (left.kind === SyntaxKind.ObjectLiteralExpression || left.kind === SyntaxKind.ArrayLiteralExpression)) {
21340-
return checkDestructuringAssignment(left, checkExpression(right, checkMode), checkMode);
21347+
return checkDestructuringAssignment(left, checkExpression(right, checkMode), checkMode, right.kind === SyntaxKind.ThisKeyword);
2134121348
}
2134221349
let leftType: Type;
2134321350
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken) {
@@ -24400,7 +24407,7 @@ namespace ts {
2440024407
const property = getPropertyOfType(parentType!, nameText)!; // TODO: GH#18217
2440124408
markPropertyAsReferenced(property, /*nodeForCheckWriteOnly*/ undefined, /*isThisAccess*/ false); // A destructuring is never a write-only reference.
2440224409
if (parent.initializer && property && !isComputedPropertyName(name)) {
24403-
checkPropertyAccessibility(parent, parent.initializer, parentType!, property);
24410+
checkPropertyAccessibility(parent, parent.initializer.kind === SyntaxKind.SuperKeyword, parentType!, property);
2440424411
}
2440524412
}
2440624413
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
tests/cases/compiler/destructuringAssignment_private.ts(6,10): error TS2341: Property 'x' is private and only accessible within class 'C'.
2+
tests/cases/compiler/destructuringAssignment_private.ts(7,4): error TS2341: Property 'o' is private and only accessible within class 'C'.
3+
4+
5+
==== tests/cases/compiler/destructuringAssignment_private.ts (2 errors) ====
6+
class C {
7+
private x = 0;
8+
private o = [{ a: 1 }];
9+
}
10+
let x: number;
11+
([{ a: { x } }] = [{ a: new C() }]);
12+
~
13+
!!! error TS2341: Property 'x' is private and only accessible within class 'C'.
14+
({ o: [{ a: x }]} = new C());
15+
~
16+
!!! error TS2341: Property 'o' is private and only accessible within class 'C'.
17+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//// [destructuringAssignment_private.ts]
2+
class C {
3+
private x = 0;
4+
private o = [{ a: 1 }];
5+
}
6+
let x: number;
7+
([{ a: { x } }] = [{ a: new C() }]);
8+
({ o: [{ a: x }]} = new C());
9+
10+
11+
//// [destructuringAssignment_private.js]
12+
var C = /** @class */ (function () {
13+
function C() {
14+
this.x = 0;
15+
this.o = [{ a: 1 }];
16+
}
17+
return C;
18+
}());
19+
var x;
20+
(x = [{ a: new C() }][0].a.x);
21+
(x = new C().o[0].a);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
=== tests/cases/compiler/destructuringAssignment_private.ts ===
2+
class C {
3+
>C : Symbol(C, Decl(destructuringAssignment_private.ts, 0, 0))
4+
5+
private x = 0;
6+
>x : Symbol(C.x, Decl(destructuringAssignment_private.ts, 0, 9))
7+
8+
private o = [{ a: 1 }];
9+
>o : Symbol(C.o, Decl(destructuringAssignment_private.ts, 1, 18))
10+
>a : Symbol(a, Decl(destructuringAssignment_private.ts, 2, 18))
11+
}
12+
let x: number;
13+
>x : Symbol(x, Decl(destructuringAssignment_private.ts, 4, 3))
14+
15+
([{ a: { x } }] = [{ a: new C() }]);
16+
>a : Symbol(a, Decl(destructuringAssignment_private.ts, 5, 3))
17+
>x : Symbol(x, Decl(destructuringAssignment_private.ts, 5, 8))
18+
>a : Symbol(a, Decl(destructuringAssignment_private.ts, 5, 20))
19+
>C : Symbol(C, Decl(destructuringAssignment_private.ts, 0, 0))
20+
21+
({ o: [{ a: x }]} = new C());
22+
>o : Symbol(o, Decl(destructuringAssignment_private.ts, 6, 2))
23+
>a : Symbol(a, Decl(destructuringAssignment_private.ts, 6, 8))
24+
>x : Symbol(x, Decl(destructuringAssignment_private.ts, 4, 3))
25+
>C : Symbol(C, Decl(destructuringAssignment_private.ts, 0, 0))
26+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
=== tests/cases/compiler/destructuringAssignment_private.ts ===
2+
class C {
3+
>C : C
4+
5+
private x = 0;
6+
>x : number
7+
>0 : 0
8+
9+
private o = [{ a: 1 }];
10+
>o : { a: number; }[]
11+
>[{ a: 1 }] : { a: number; }[]
12+
>{ a: 1 } : { a: number; }
13+
>a : number
14+
>1 : 1
15+
}
16+
let x: number;
17+
>x : number
18+
19+
([{ a: { x } }] = [{ a: new C() }]);
20+
>([{ a: { x } }] = [{ a: new C() }]) : [{ a: C; }]
21+
>[{ a: { x } }] = [{ a: new C() }] : [{ a: C; }]
22+
>[{ a: { x } }] : [{ a: { x: number; }; }]
23+
>{ a: { x } } : { a: { x: number; }; }
24+
>a : { x: number; }
25+
>{ x } : { x: number; }
26+
>x : number
27+
>[{ a: new C() }] : [{ a: C; }]
28+
>{ a: new C() } : { a: C; }
29+
>a : C
30+
>new C() : C
31+
>C : typeof C
32+
33+
({ o: [{ a: x }]} = new C());
34+
>({ o: [{ a: x }]} = new C()) : C
35+
>{ o: [{ a: x }]} = new C() : C
36+
>{ o: [{ a: x }]} : { o: [{ a: number; }]; }
37+
>o : [{ a: number; }]
38+
>[{ a: x }] : [{ a: number; }]
39+
>{ a: x } : { a: number; }
40+
>a : number
41+
>x : number
42+
>new C() : C
43+
>C : typeof C
44+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
tests/cases/compiler/noUnusedLocals_destructuringAssignment.ts(10,13): error TS6133: 'f' is declared but its value is never read.
2+
3+
4+
==== tests/cases/compiler/noUnusedLocals_destructuringAssignment.ts (1 errors) ====
5+
class C {
6+
private x = 0;
7+
8+
m(): number {
9+
let x: number;
10+
({ x } = this);
11+
return x;
12+
}
13+
14+
private f(): Function {
15+
~
16+
!!! error TS6133: 'f' is declared but its value is never read.
17+
let f: Function;
18+
({ f } = this);
19+
return f;
20+
}
21+
}
22+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//// [noUnusedLocals_destructuringAssignment.ts]
2+
class C {
3+
private x = 0;
4+
5+
m(): number {
6+
let x: number;
7+
({ x } = this);
8+
return x;
9+
}
10+
11+
private f(): Function {
12+
let f: Function;
13+
({ f } = this);
14+
return f;
15+
}
16+
}
17+
18+
19+
//// [noUnusedLocals_destructuringAssignment.js]
20+
var C = /** @class */ (function () {
21+
function C() {
22+
this.x = 0;
23+
}
24+
C.prototype.m = function () {
25+
var x;
26+
(x = this.x);
27+
return x;
28+
};
29+
C.prototype.f = function () {
30+
var f;
31+
(f = this.f);
32+
return f;
33+
};
34+
return C;
35+
}());

0 commit comments

Comments
 (0)