Skip to content

Commit 0b3b4ea

Browse files
authored
Added error for class properties used within their own declaration (#29395)
* Added error for class properties used within their own declaration Fixes #5987. Usages of a class property in a preceding property already gave an error, but the following doesn't yet: ```ts class Test { x: number = this.x; } ``` As with other use-before-declare checking, IIFEs are not treated as invalid uses. * Accepted 'witness' baselines; removed unnecessary !== * Addressed quick feedback items * Accepted odd new baseline * Fixed post-merge introduced lint errors * Updated baselines again
2 parents d60c6a0 + 0c832f2 commit 0b3b4ea

7 files changed

+478
-1
lines changed

Diff for: src/compiler/checker.ts

+38
Original file line numberDiff line numberDiff line change
@@ -1130,6 +1130,10 @@ namespace ts {
11301130
// still might be illegal if the usage is within a computed property name in the class (eg class A { static p = "a"; [A.p]() {} })
11311131
return !findAncestor(usage, n => isComputedPropertyName(n) && n.parent.parent === declaration);
11321132
}
1133+
else if (isPropertyDeclaration(declaration)) {
1134+
// still might be illegal if a self-referencing property initializer (eg private x = this.x)
1135+
return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage);
1136+
}
11331137
return true;
11341138
}
11351139

@@ -1204,6 +1208,40 @@ namespace ts {
12041208
return false;
12051209
});
12061210
}
1211+
1212+
function isPropertyImmediatelyReferencedWithinDeclaration(declaration: PropertyDeclaration, usage: Node) {
1213+
// always legal if usage is after declaration
1214+
if (usage.end > declaration.end) {
1215+
return false;
1216+
}
1217+
1218+
// still might be legal if usage is deferred (e.g. x: any = () => this.x)
1219+
// otherwise illegal if immediately referenced within the declaration (e.g. x: any = this.x)
1220+
const ancestorChangingReferenceScope = findAncestor(usage, (node: Node) => {
1221+
if (node === declaration) {
1222+
return "quit";
1223+
}
1224+
1225+
switch (node.kind) {
1226+
case SyntaxKind.ArrowFunction:
1227+
case SyntaxKind.PropertyDeclaration:
1228+
return true;
1229+
case SyntaxKind.Block:
1230+
switch (node.parent.kind) {
1231+
case SyntaxKind.GetAccessor:
1232+
case SyntaxKind.MethodDeclaration:
1233+
case SyntaxKind.SetAccessor:
1234+
return true;
1235+
default:
1236+
return false;
1237+
}
1238+
default:
1239+
return false;
1240+
}
1241+
});
1242+
1243+
return ancestorChangingReferenceScope === undefined;
1244+
}
12071245
}
12081246

12091247
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
tests/cases/compiler/classUsedBeforeInitializedVariables.ts(4,15): error TS2729: Property 'p4' is used before its initialization.
2+
tests/cases/compiler/classUsedBeforeInitializedVariables.ts(7,34): error TS2729: Property 'directlyAssigned' is used before its initialization.
3+
tests/cases/compiler/classUsedBeforeInitializedVariables.ts(16,15): error TS2729: Property 'withinObjectLiteral' is used before its initialization.
4+
tests/cases/compiler/classUsedBeforeInitializedVariables.ts(20,19): error TS2729: Property 'withinObjectLiteralGetterName' is used before its initialization.
5+
tests/cases/compiler/classUsedBeforeInitializedVariables.ts(26,19): error TS2729: Property 'withinObjectLiteralSetterName' is used before its initialization.
6+
tests/cases/compiler/classUsedBeforeInitializedVariables.ts(29,64): error TS2729: Property 'withinClassDeclarationExtension' is used before its initialization.
7+
8+
9+
==== tests/cases/compiler/classUsedBeforeInitializedVariables.ts (6 errors) ====
10+
class Test {
11+
p1 = 0;
12+
p2 = this.p1;
13+
p3 = this.p4;
14+
~~
15+
!!! error TS2729: Property 'p4' is used before its initialization.
16+
!!! related TS2728 tests/cases/compiler/classUsedBeforeInitializedVariables.ts:5:5: 'p4' is declared here.
17+
p4 = 0;
18+
19+
directlyAssigned: any = this.directlyAssigned;
20+
~~~~~~~~~~~~~~~~
21+
!!! error TS2729: Property 'directlyAssigned' is used before its initialization.
22+
!!! related TS2728 tests/cases/compiler/classUsedBeforeInitializedVariables.ts:7:5: 'directlyAssigned' is declared here.
23+
24+
withinArrowFunction: any = () => this.withinArrowFunction;
25+
26+
withinFunction: any = function () {
27+
return this.withinFunction;
28+
};
29+
30+
withinObjectLiteral: any = {
31+
[this.withinObjectLiteral]: true,
32+
~~~~~~~~~~~~~~~~~~~
33+
!!! error TS2729: Property 'withinObjectLiteral' is used before its initialization.
34+
!!! related TS2728 tests/cases/compiler/classUsedBeforeInitializedVariables.ts:15:5: 'withinObjectLiteral' is declared here.
35+
};
36+
37+
withinObjectLiteralGetterName: any = {
38+
get [this.withinObjectLiteralGetterName]() {
39+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
40+
!!! error TS2729: Property 'withinObjectLiteralGetterName' is used before its initialization.
41+
!!! related TS2728 tests/cases/compiler/classUsedBeforeInitializedVariables.ts:19:5: 'withinObjectLiteralGetterName' is declared here.
42+
return true;
43+
}
44+
};
45+
46+
withinObjectLiteralSetterName: any = {
47+
set [this.withinObjectLiteralSetterName](_: any) {}
48+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
49+
!!! error TS2729: Property 'withinObjectLiteralSetterName' is used before its initialization.
50+
!!! related TS2728 tests/cases/compiler/classUsedBeforeInitializedVariables.ts:25:5: 'withinObjectLiteralSetterName' is declared here.
51+
};
52+
53+
withinClassDeclarationExtension: any = (class extends this.withinClassDeclarationExtension { });
54+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
55+
!!! error TS2729: Property 'withinClassDeclarationExtension' is used before its initialization.
56+
!!! related TS2728 tests/cases/compiler/classUsedBeforeInitializedVariables.ts:29:5: 'withinClassDeclarationExtension' is declared here.
57+
58+
// These error cases are ignored (not checked by control flow analysis)
59+
60+
assignedByArrowFunction: any = (() => this.assignedByFunction)();
61+
62+
assignedByFunction: any = (function () {
63+
return this.assignedByFunction;
64+
})();
65+
}
66+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//// [classUsedBeforeInitializedVariables.ts]
2+
class Test {
3+
p1 = 0;
4+
p2 = this.p1;
5+
p3 = this.p4;
6+
p4 = 0;
7+
8+
directlyAssigned: any = this.directlyAssigned;
9+
10+
withinArrowFunction: any = () => this.withinArrowFunction;
11+
12+
withinFunction: any = function () {
13+
return this.withinFunction;
14+
};
15+
16+
withinObjectLiteral: any = {
17+
[this.withinObjectLiteral]: true,
18+
};
19+
20+
withinObjectLiteralGetterName: any = {
21+
get [this.withinObjectLiteralGetterName]() {
22+
return true;
23+
}
24+
};
25+
26+
withinObjectLiteralSetterName: any = {
27+
set [this.withinObjectLiteralSetterName](_: any) {}
28+
};
29+
30+
withinClassDeclarationExtension: any = (class extends this.withinClassDeclarationExtension { });
31+
32+
// These error cases are ignored (not checked by control flow analysis)
33+
34+
assignedByArrowFunction: any = (() => this.assignedByFunction)();
35+
36+
assignedByFunction: any = (function () {
37+
return this.assignedByFunction;
38+
})();
39+
}
40+
41+
42+
//// [classUsedBeforeInitializedVariables.js]
43+
var __extends = (this && this.__extends) || (function () {
44+
var extendStatics = function (d, b) {
45+
extendStatics = Object.setPrototypeOf ||
46+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
47+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
48+
return extendStatics(d, b);
49+
};
50+
return function (d, b) {
51+
extendStatics(d, b);
52+
function __() { this.constructor = d; }
53+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
54+
};
55+
})();
56+
var Test = /** @class */ (function () {
57+
function Test() {
58+
var _a, _b, _c;
59+
var _this = this;
60+
this.p1 = 0;
61+
this.p2 = this.p1;
62+
this.p3 = this.p4;
63+
this.p4 = 0;
64+
this.directlyAssigned = this.directlyAssigned;
65+
this.withinArrowFunction = function () { return _this.withinArrowFunction; };
66+
this.withinFunction = function () {
67+
return this.withinFunction;
68+
};
69+
this.withinObjectLiteral = (_a = {},
70+
_a[this.withinObjectLiteral] = true,
71+
_a);
72+
this.withinObjectLiteralGetterName = (_b = {},
73+
Object.defineProperty(_b, this.withinObjectLiteralGetterName, {
74+
get: function () {
75+
return true;
76+
},
77+
enumerable: true,
78+
configurable: true
79+
}),
80+
_b);
81+
this.withinObjectLiteralSetterName = (_c = {},
82+
Object.defineProperty(_c, this.withinObjectLiteralSetterName, {
83+
set: function (_) { },
84+
enumerable: true,
85+
configurable: true
86+
}),
87+
_c);
88+
this.withinClassDeclarationExtension = (/** @class */ (function (_super) {
89+
__extends(class_1, _super);
90+
function class_1() {
91+
return _super !== null && _super.apply(this, arguments) || this;
92+
}
93+
return class_1;
94+
}(this.withinClassDeclarationExtension)));
95+
// These error cases are ignored (not checked by control flow analysis)
96+
this.assignedByArrowFunction = (function () { return _this.assignedByFunction; })();
97+
this.assignedByFunction = (function () {
98+
return this.assignedByFunction;
99+
})();
100+
}
101+
return Test;
102+
}());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
=== tests/cases/compiler/classUsedBeforeInitializedVariables.ts ===
2+
class Test {
3+
>Test : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0))
4+
5+
p1 = 0;
6+
>p1 : Symbol(Test.p1, Decl(classUsedBeforeInitializedVariables.ts, 0, 12))
7+
8+
p2 = this.p1;
9+
>p2 : Symbol(Test.p2, Decl(classUsedBeforeInitializedVariables.ts, 1, 11))
10+
>this.p1 : Symbol(Test.p1, Decl(classUsedBeforeInitializedVariables.ts, 0, 12))
11+
>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0))
12+
>p1 : Symbol(Test.p1, Decl(classUsedBeforeInitializedVariables.ts, 0, 12))
13+
14+
p3 = this.p4;
15+
>p3 : Symbol(Test.p3, Decl(classUsedBeforeInitializedVariables.ts, 2, 17))
16+
>this.p4 : Symbol(Test.p4, Decl(classUsedBeforeInitializedVariables.ts, 3, 17))
17+
>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0))
18+
>p4 : Symbol(Test.p4, Decl(classUsedBeforeInitializedVariables.ts, 3, 17))
19+
20+
p4 = 0;
21+
>p4 : Symbol(Test.p4, Decl(classUsedBeforeInitializedVariables.ts, 3, 17))
22+
23+
directlyAssigned: any = this.directlyAssigned;
24+
>directlyAssigned : Symbol(Test.directlyAssigned, Decl(classUsedBeforeInitializedVariables.ts, 4, 11))
25+
>this.directlyAssigned : Symbol(Test.directlyAssigned, Decl(classUsedBeforeInitializedVariables.ts, 4, 11))
26+
>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0))
27+
>directlyAssigned : Symbol(Test.directlyAssigned, Decl(classUsedBeforeInitializedVariables.ts, 4, 11))
28+
29+
withinArrowFunction: any = () => this.withinArrowFunction;
30+
>withinArrowFunction : Symbol(Test.withinArrowFunction, Decl(classUsedBeforeInitializedVariables.ts, 6, 50))
31+
>this.withinArrowFunction : Symbol(Test.withinArrowFunction, Decl(classUsedBeforeInitializedVariables.ts, 6, 50))
32+
>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0))
33+
>withinArrowFunction : Symbol(Test.withinArrowFunction, Decl(classUsedBeforeInitializedVariables.ts, 6, 50))
34+
35+
withinFunction: any = function () {
36+
>withinFunction : Symbol(Test.withinFunction, Decl(classUsedBeforeInitializedVariables.ts, 8, 62))
37+
38+
return this.withinFunction;
39+
};
40+
41+
withinObjectLiteral: any = {
42+
>withinObjectLiteral : Symbol(Test.withinObjectLiteral, Decl(classUsedBeforeInitializedVariables.ts, 12, 6))
43+
44+
[this.withinObjectLiteral]: true,
45+
>[this.withinObjectLiteral] : Symbol([this.withinObjectLiteral], Decl(classUsedBeforeInitializedVariables.ts, 14, 32))
46+
>this.withinObjectLiteral : Symbol(Test.withinObjectLiteral, Decl(classUsedBeforeInitializedVariables.ts, 12, 6))
47+
>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0))
48+
>withinObjectLiteral : Symbol(Test.withinObjectLiteral, Decl(classUsedBeforeInitializedVariables.ts, 12, 6))
49+
50+
};
51+
52+
withinObjectLiteralGetterName: any = {
53+
>withinObjectLiteralGetterName : Symbol(Test.withinObjectLiteralGetterName, Decl(classUsedBeforeInitializedVariables.ts, 16, 6))
54+
55+
get [this.withinObjectLiteralGetterName]() {
56+
>[this.withinObjectLiteralGetterName] : Symbol([this.withinObjectLiteralGetterName], Decl(classUsedBeforeInitializedVariables.ts, 18, 42))
57+
>this.withinObjectLiteralGetterName : Symbol(Test.withinObjectLiteralGetterName, Decl(classUsedBeforeInitializedVariables.ts, 16, 6))
58+
>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0))
59+
>withinObjectLiteralGetterName : Symbol(Test.withinObjectLiteralGetterName, Decl(classUsedBeforeInitializedVariables.ts, 16, 6))
60+
61+
return true;
62+
}
63+
};
64+
65+
withinObjectLiteralSetterName: any = {
66+
>withinObjectLiteralSetterName : Symbol(Test.withinObjectLiteralSetterName, Decl(classUsedBeforeInitializedVariables.ts, 22, 6))
67+
68+
set [this.withinObjectLiteralSetterName](_: any) {}
69+
>[this.withinObjectLiteralSetterName] : Symbol([this.withinObjectLiteralSetterName], Decl(classUsedBeforeInitializedVariables.ts, 24, 42))
70+
>this.withinObjectLiteralSetterName : Symbol(Test.withinObjectLiteralSetterName, Decl(classUsedBeforeInitializedVariables.ts, 22, 6))
71+
>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0))
72+
>withinObjectLiteralSetterName : Symbol(Test.withinObjectLiteralSetterName, Decl(classUsedBeforeInitializedVariables.ts, 22, 6))
73+
>_ : Symbol(_, Decl(classUsedBeforeInitializedVariables.ts, 25, 49))
74+
75+
};
76+
77+
withinClassDeclarationExtension: any = (class extends this.withinClassDeclarationExtension { });
78+
>withinClassDeclarationExtension : Symbol(Test.withinClassDeclarationExtension, Decl(classUsedBeforeInitializedVariables.ts, 26, 6))
79+
>this.withinClassDeclarationExtension : Symbol(Test.withinClassDeclarationExtension, Decl(classUsedBeforeInitializedVariables.ts, 26, 6))
80+
>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0))
81+
>withinClassDeclarationExtension : Symbol(Test.withinClassDeclarationExtension, Decl(classUsedBeforeInitializedVariables.ts, 26, 6))
82+
83+
// These error cases are ignored (not checked by control flow analysis)
84+
85+
assignedByArrowFunction: any = (() => this.assignedByFunction)();
86+
>assignedByArrowFunction : Symbol(Test.assignedByArrowFunction, Decl(classUsedBeforeInitializedVariables.ts, 28, 100))
87+
>this.assignedByFunction : Symbol(Test.assignedByFunction, Decl(classUsedBeforeInitializedVariables.ts, 32, 69))
88+
>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0))
89+
>assignedByFunction : Symbol(Test.assignedByFunction, Decl(classUsedBeforeInitializedVariables.ts, 32, 69))
90+
91+
assignedByFunction: any = (function () {
92+
>assignedByFunction : Symbol(Test.assignedByFunction, Decl(classUsedBeforeInitializedVariables.ts, 32, 69))
93+
94+
return this.assignedByFunction;
95+
})();
96+
}
97+

0 commit comments

Comments
 (0)