Skip to content

Commit c1e45ac

Browse files
authored
Fix use-before-def errors for ESNext property declarations (microsoft#36465)
* Fix use-before-def errors for ESNext property declarations Fixes microsoft#36441 Fixes microsoft#36442 * Handle property declarations in nested classes
1 parent 8a0b882 commit c1e45ac

6 files changed

+258
-14
lines changed

src/compiler/checker.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1327,12 +1327,14 @@ namespace ts {
13271327
}
13281328
else if (isPropertyDeclaration(declaration)) {
13291329
// still might be illegal if a self-referencing property initializer (eg private x = this.x)
1330-
return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage);
1330+
return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ false);
13311331
}
13321332
else if (isParameterPropertyDeclaration(declaration, declaration.parent)) {
1333+
const container = getEnclosingBlockScopeContainer(declaration.parent);
13331334
// foo = this.bar is illegal in esnext+useDefineForClassFields when bar is a parameter property
13341335
return !(compilerOptions.target === ScriptTarget.ESNext && !!compilerOptions.useDefineForClassFields
1335-
&& isUsedInFunctionOrInstanceProperty(usage, declaration));
1336+
&& getContainingClass(declaration) === getContainingClass(usage)
1337+
&& isUsedInFunctionOrInstanceProperty(usage, declaration, container));
13361338
}
13371339
return true;
13381340
}
@@ -1358,10 +1360,19 @@ namespace ts {
13581360
}
13591361

13601362
const container = getEnclosingBlockScopeContainer(declaration);
1361-
return !!(usage.flags & NodeFlags.JSDoc)
1362-
|| isInTypeQuery(usage)
1363-
|| isUsedInFunctionOrInstanceProperty(usage, declaration, container)
1364-
&& !(compilerOptions.target === ScriptTarget.ESNext && !!compilerOptions.useDefineForClassFields);
1363+
if (!!(usage.flags & NodeFlags.JSDoc) || isInTypeQuery(usage)) {
1364+
return true;
1365+
}
1366+
if (isUsedInFunctionOrInstanceProperty(usage, declaration, container)) {
1367+
if (compilerOptions.target === ScriptTarget.ESNext && !!compilerOptions.useDefineForClassFields) {
1368+
return (isPropertyDeclaration(declaration) || isParameterPropertyDeclaration(declaration, declaration.parent)) &&
1369+
!isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ true);
1370+
}
1371+
else {
1372+
return true;
1373+
}
1374+
}
1375+
return false;
13651376

13661377
function isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration: VariableDeclaration, usage: Node): boolean {
13671378
const container = getEnclosingBlockScopeContainer(declaration);
@@ -1413,7 +1424,8 @@ namespace ts {
14131424
});
14141425
}
14151426

1416-
function isPropertyImmediatelyReferencedWithinDeclaration(declaration: PropertyDeclaration, usage: Node) {
1427+
/** stopAtAnyPropertyDeclaration is used for detecting ES-standard class field use-before-def errors */
1428+
function isPropertyImmediatelyReferencedWithinDeclaration(declaration: PropertyDeclaration | ParameterPropertyDeclaration, usage: Node, stopAtAnyPropertyDeclaration: boolean) {
14171429
// always legal if usage is after declaration
14181430
if (usage.end > declaration.end) {
14191431
return false;
@@ -1428,8 +1440,13 @@ namespace ts {
14281440

14291441
switch (node.kind) {
14301442
case SyntaxKind.ArrowFunction:
1431-
case SyntaxKind.PropertyDeclaration:
14321443
return true;
1444+
case SyntaxKind.PropertyDeclaration:
1445+
// even when stopping at any property declaration, they need to come from the same class
1446+
return stopAtAnyPropertyDeclaration &&
1447+
(isPropertyDeclaration(declaration) && node.parent === declaration.parent
1448+
|| isParameterPropertyDeclaration(declaration, declaration.parent) && node.parent === declaration.parent.parent)
1449+
? "quit": true;
14331450
case SyntaxKind.Block:
14341451
switch (node.parent.kind) {
14351452
case SyntaxKind.GetAccessor:

tests/baselines/reference/assignParameterPropertyToPropertyDeclarationESNext.errors.txt

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterProper
1818
m1() {
1919
this.foo // ok
2020
}
21-
constructor(private foo: string) {}
21+
constructor(public foo: string) {}
2222
quim = this.baz // should error
2323
~~~
2424
!!! error TS2729: Property 'baz' is used before its initialization.
@@ -32,4 +32,27 @@ tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterProper
3232
this.foo // ok
3333
}
3434
}
35+
36+
class D extends C {
37+
quill = this.foo // ok
38+
}
39+
40+
class E {
41+
bar = () => this.foo1 + this.foo2; // both ok
42+
foo1 = '';
43+
constructor(public foo2: string) {}
44+
}
45+
46+
class F {
47+
Inner = class extends F {
48+
p2 = this.p1
49+
}
50+
p1 = 0
51+
}
52+
class G {
53+
Inner = class extends G {
54+
p2 = this.p1
55+
}
56+
constructor(public p1: number) {}
57+
}
3558

tests/baselines/reference/assignParameterPropertyToPropertyDeclarationESNext.js

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,37 @@ class C {
66
m1() {
77
this.foo // ok
88
}
9-
constructor(private foo: string) {}
9+
constructor(public foo: string) {}
1010
quim = this.baz // should error
1111
baz = this.foo; // should error
1212
quid = this.baz // ok
1313
m2() {
1414
this.foo // ok
1515
}
1616
}
17+
18+
class D extends C {
19+
quill = this.foo // ok
20+
}
21+
22+
class E {
23+
bar = () => this.foo1 + this.foo2; // both ok
24+
foo1 = '';
25+
constructor(public foo2: string) {}
26+
}
27+
28+
class F {
29+
Inner = class extends F {
30+
p2 = this.p1
31+
}
32+
p1 = 0
33+
}
34+
class G {
35+
Inner = class extends G {
36+
p2 = this.p1
37+
}
38+
constructor(public p1: number) {}
39+
}
1740

1841

1942
//// [assignParameterPropertyToPropertyDeclarationESNext.js]
@@ -35,3 +58,29 @@ class C {
3558
this.foo; // ok
3659
}
3760
}
61+
class D extends C {
62+
quill = this.foo; // ok
63+
}
64+
class E {
65+
foo2;
66+
bar = () => this.foo1 + this.foo2; // both ok
67+
foo1 = '';
68+
constructor(foo2) {
69+
this.foo2 = foo2;
70+
}
71+
}
72+
class F {
73+
Inner = class extends F {
74+
p2 = this.p1;
75+
};
76+
p1 = 0;
77+
}
78+
class G {
79+
p1;
80+
Inner = class extends G {
81+
p2 = this.p1;
82+
};
83+
constructor(p1) {
84+
this.p1 = p1;
85+
}
86+
}

tests/baselines/reference/assignParameterPropertyToPropertyDeclarationESNext.symbols

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ class C {
2828
>this : Symbol(C, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 0, 0))
2929
>foo : Symbol(C.foo, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 16))
3030
}
31-
constructor(private foo: string) {}
31+
constructor(public foo: string) {}
3232
>foo : Symbol(C.foo, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 16))
3333

3434
quim = this.baz // should error
35-
>quim : Symbol(C.quim, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 39))
35+
>quim : Symbol(C.quim, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 38))
3636
>this.baz : Symbol(C.baz, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 8, 19))
3737
>this : Symbol(C, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 0, 0))
3838
>baz : Symbol(C.baz, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 8, 19))
@@ -59,3 +59,66 @@ class C {
5959
}
6060
}
6161

62+
class D extends C {
63+
>D : Symbol(D, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 14, 1))
64+
>C : Symbol(C, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 0, 0))
65+
66+
quill = this.foo // ok
67+
>quill : Symbol(D.quill, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 16, 19))
68+
>this.foo : Symbol(C.foo, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 16))
69+
>this : Symbol(D, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 14, 1))
70+
>foo : Symbol(C.foo, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 16))
71+
}
72+
73+
class E {
74+
>E : Symbol(E, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 18, 1))
75+
76+
bar = () => this.foo1 + this.foo2; // both ok
77+
>bar : Symbol(E.bar, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 20, 9))
78+
>this.foo1 : Symbol(E.foo1, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 21, 38))
79+
>this : Symbol(E, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 18, 1))
80+
>foo1 : Symbol(E.foo1, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 21, 38))
81+
>this.foo2 : Symbol(E.foo2, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 23, 16))
82+
>this : Symbol(E, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 18, 1))
83+
>foo2 : Symbol(E.foo2, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 23, 16))
84+
85+
foo1 = '';
86+
>foo1 : Symbol(E.foo1, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 21, 38))
87+
88+
constructor(public foo2: string) {}
89+
>foo2 : Symbol(E.foo2, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 23, 16))
90+
}
91+
92+
class F {
93+
>F : Symbol(F, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 24, 1))
94+
95+
Inner = class extends F {
96+
>Inner : Symbol(F.Inner, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 26, 9))
97+
>F : Symbol(F, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 24, 1))
98+
99+
p2 = this.p1
100+
>p2 : Symbol((Anonymous class).p2, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 27, 29))
101+
>this.p1 : Symbol(F.p1, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 29, 5))
102+
>this : Symbol((Anonymous class), Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 27, 11))
103+
>p1 : Symbol(F.p1, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 29, 5))
104+
}
105+
p1 = 0
106+
>p1 : Symbol(F.p1, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 29, 5))
107+
}
108+
class G {
109+
>G : Symbol(G, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 31, 1))
110+
111+
Inner = class extends G {
112+
>Inner : Symbol(G.Inner, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 32, 9))
113+
>G : Symbol(G, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 31, 1))
114+
115+
p2 = this.p1
116+
>p2 : Symbol((Anonymous class).p2, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 33, 29))
117+
>this.p1 : Symbol(G.p1, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 36, 16))
118+
>this : Symbol((Anonymous class), Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 33, 11))
119+
>p1 : Symbol(G.p1, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 36, 16))
120+
}
121+
constructor(public p1: number) {}
122+
>p1 : Symbol(G.p1, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 36, 16))
123+
}
124+

tests/baselines/reference/assignParameterPropertyToPropertyDeclarationESNext.types

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class C {
2828
>this : this
2929
>foo : string
3030
}
31-
constructor(private foo: string) {}
31+
constructor(public foo: string) {}
3232
>foo : string
3333

3434
quim = this.baz // should error
@@ -59,3 +59,72 @@ class C {
5959
}
6060
}
6161

62+
class D extends C {
63+
>D : D
64+
>C : C
65+
66+
quill = this.foo // ok
67+
>quill : string
68+
>this.foo : string
69+
>this : this
70+
>foo : string
71+
}
72+
73+
class E {
74+
>E : E
75+
76+
bar = () => this.foo1 + this.foo2; // both ok
77+
>bar : () => string
78+
>() => this.foo1 + this.foo2 : () => string
79+
>this.foo1 + this.foo2 : string
80+
>this.foo1 : string
81+
>this : this
82+
>foo1 : string
83+
>this.foo2 : string
84+
>this : this
85+
>foo2 : string
86+
87+
foo1 = '';
88+
>foo1 : string
89+
>'' : ""
90+
91+
constructor(public foo2: string) {}
92+
>foo2 : string
93+
}
94+
95+
class F {
96+
>F : F
97+
98+
Inner = class extends F {
99+
>Inner : typeof (Anonymous class)
100+
>class extends F { p2 = this.p1 } : typeof (Anonymous class)
101+
>F : F
102+
103+
p2 = this.p1
104+
>p2 : number
105+
>this.p1 : number
106+
>this : this
107+
>p1 : number
108+
}
109+
p1 = 0
110+
>p1 : number
111+
>0 : 0
112+
}
113+
class G {
114+
>G : G
115+
116+
Inner = class extends G {
117+
>Inner : typeof (Anonymous class)
118+
>class extends G { p2 = this.p1 } : typeof (Anonymous class)
119+
>G : G
120+
121+
p2 = this.p1
122+
>p2 : number
123+
>this.p1 : number
124+
>this : this
125+
>p1 : number
126+
}
127+
constructor(public p1: number) {}
128+
>p1 : number
129+
}
130+

tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationESNext.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,34 @@ class C {
77
m1() {
88
this.foo // ok
99
}
10-
constructor(private foo: string) {}
10+
constructor(public foo: string) {}
1111
quim = this.baz // should error
1212
baz = this.foo; // should error
1313
quid = this.baz // ok
1414
m2() {
1515
this.foo // ok
1616
}
1717
}
18+
19+
class D extends C {
20+
quill = this.foo // ok
21+
}
22+
23+
class E {
24+
bar = () => this.foo1 + this.foo2; // both ok
25+
foo1 = '';
26+
constructor(public foo2: string) {}
27+
}
28+
29+
class F {
30+
Inner = class extends F {
31+
p2 = this.p1
32+
}
33+
p1 = 0
34+
}
35+
class G {
36+
Inner = class extends G {
37+
p2 = this.p1
38+
}
39+
constructor(public p1: number) {}
40+
}

0 commit comments

Comments
 (0)