ESNext+[[Define]]: reference to param props illegal (#36425)
* ESNext+[[Define]]: reference to param props illegal When target: "esnext" and useDefineForClassFields: true, property declaration initialisers should not be able to reference parameter properties; class fields are initialised before the constructor runs, but parameter properties are not: ```ts class C { foo = this.bar constructor(public bar: string) { } } ``` emits code that looks like this: ```js class C { bar foo = this.bar constructor(bar) { this.bar = bar } } new C('x').foo.length // crashes; foo is undefined ``` This PR adds an error on foo's declaration with ESNext+[[Define]]. * improve test
This commit is contained in:
parent
43fc19c958
commit
368db997ed
|
@ -1328,6 +1328,11 @@ namespace ts {
|
||||||
// still might be illegal if a self-referencing property initializer (eg private x = this.x)
|
// still might be illegal if a self-referencing property initializer (eg private x = this.x)
|
||||||
return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage);
|
return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage);
|
||||||
}
|
}
|
||||||
|
else if (isParameterPropertyDeclaration(declaration, declaration.parent)) {
|
||||||
|
// foo = this.bar is illegal in esnext+useDefineForClassFields when bar is a parameter property
|
||||||
|
return !(compilerOptions.target === ScriptTarget.ESNext && !!compilerOptions.useDefineForClassFields
|
||||||
|
&& isUsedInFunctionOrInstanceProperty(usage, declaration));
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1336,6 +1341,7 @@ namespace ts {
|
||||||
// 1. inside an export specifier
|
// 1. inside an export specifier
|
||||||
// 2. inside a function
|
// 2. inside a function
|
||||||
// 3. inside an instance property initializer, a reference to a non-instance property
|
// 3. inside an instance property initializer, a reference to a non-instance property
|
||||||
|
// (except when target: "esnext" and useDefineForClassFields: true and the reference is to a parameter property)
|
||||||
// 4. inside a static property initializer, a reference to a static method in the same class
|
// 4. inside a static property initializer, a reference to a static method in the same class
|
||||||
// 5. inside a TS export= declaration (since we will move the export statement during emit to avoid TDZ)
|
// 5. inside a TS export= declaration (since we will move the export statement during emit to avoid TDZ)
|
||||||
// or if usage is in a type context:
|
// or if usage is in a type context:
|
||||||
|
@ -1351,7 +1357,10 @@ namespace ts {
|
||||||
}
|
}
|
||||||
|
|
||||||
const container = getEnclosingBlockScopeContainer(declaration);
|
const container = getEnclosingBlockScopeContainer(declaration);
|
||||||
return !!(usage.flags & NodeFlags.JSDoc) || isInTypeQuery(usage) || isUsedInFunctionOrInstanceProperty(usage, declaration, container);
|
return !!(usage.flags & NodeFlags.JSDoc)
|
||||||
|
|| isInTypeQuery(usage)
|
||||||
|
|| isUsedInFunctionOrInstanceProperty(usage, declaration, container)
|
||||||
|
&& !(compilerOptions.target === ScriptTarget.ESNext && !!compilerOptions.useDefineForClassFields);
|
||||||
|
|
||||||
function isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration: VariableDeclaration, usage: Node): boolean {
|
function isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration: VariableDeclaration, usage: Node): boolean {
|
||||||
const container = getEnclosingBlockScopeContainer(declaration);
|
const container = getEnclosingBlockScopeContainer(declaration);
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationESNext.ts(2,16): error TS2729: Property 'bar' is used before its initialization.
|
||||||
|
tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationESNext.ts(3,16): error TS2729: Property 'foo' is used before its initialization.
|
||||||
|
tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationESNext.ts(9,17): error TS2729: Property 'baz' is used before its initialization.
|
||||||
|
tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationESNext.ts(10,16): error TS2729: Property 'foo' is used before its initialization.
|
||||||
|
|
||||||
|
|
||||||
|
==== tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationESNext.ts (4 errors) ====
|
||||||
|
class C {
|
||||||
|
qux = this.bar // should error
|
||||||
|
~~~
|
||||||
|
!!! error TS2729: Property 'bar' is used before its initialization.
|
||||||
|
!!! related TS2728 tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationESNext.ts:3:5: 'bar' is declared here.
|
||||||
|
bar = this.foo // should error
|
||||||
|
~~~
|
||||||
|
!!! error TS2729: Property 'foo' is used before its initialization.
|
||||||
|
!!! related TS2728 tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationESNext.ts:8:17: 'foo' is declared here.
|
||||||
|
quiz = this.bar // ok
|
||||||
|
m1() {
|
||||||
|
this.foo // ok
|
||||||
|
}
|
||||||
|
constructor(private foo: string) {}
|
||||||
|
quim = this.baz // should error
|
||||||
|
~~~
|
||||||
|
!!! error TS2729: Property 'baz' is used before its initialization.
|
||||||
|
!!! related TS2728 tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationESNext.ts:10:5: 'baz' is declared here.
|
||||||
|
baz = this.foo; // should error
|
||||||
|
~~~
|
||||||
|
!!! error TS2729: Property 'foo' is used before its initialization.
|
||||||
|
!!! related TS2728 tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationESNext.ts:8:17: 'foo' is declared here.
|
||||||
|
quid = this.baz // ok
|
||||||
|
m2() {
|
||||||
|
this.foo // ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
//// [assignParameterPropertyToPropertyDeclarationESNext.ts]
|
||||||
|
class C {
|
||||||
|
qux = this.bar // should error
|
||||||
|
bar = this.foo // should error
|
||||||
|
quiz = this.bar // ok
|
||||||
|
m1() {
|
||||||
|
this.foo // ok
|
||||||
|
}
|
||||||
|
constructor(private foo: string) {}
|
||||||
|
quim = this.baz // should error
|
||||||
|
baz = this.foo; // should error
|
||||||
|
quid = this.baz // ok
|
||||||
|
m2() {
|
||||||
|
this.foo // ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//// [assignParameterPropertyToPropertyDeclarationESNext.js]
|
||||||
|
class C {
|
||||||
|
foo;
|
||||||
|
qux = this.bar; // should error
|
||||||
|
bar = this.foo; // should error
|
||||||
|
quiz = this.bar; // ok
|
||||||
|
m1() {
|
||||||
|
this.foo; // ok
|
||||||
|
}
|
||||||
|
constructor(foo) {
|
||||||
|
this.foo = foo;
|
||||||
|
}
|
||||||
|
quim = this.baz; // should error
|
||||||
|
baz = this.foo; // should error
|
||||||
|
quid = this.baz; // ok
|
||||||
|
m2() {
|
||||||
|
this.foo; // ok
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
=== tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationESNext.ts ===
|
||||||
|
class C {
|
||||||
|
>C : Symbol(C, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 0, 0))
|
||||||
|
|
||||||
|
qux = this.bar // should error
|
||||||
|
>qux : Symbol(C.qux, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 0, 9))
|
||||||
|
>this.bar : Symbol(C.bar, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 1, 18))
|
||||||
|
>this : Symbol(C, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 0, 0))
|
||||||
|
>bar : Symbol(C.bar, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 1, 18))
|
||||||
|
|
||||||
|
bar = this.foo // should error
|
||||||
|
>bar : Symbol(C.bar, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 1, 18))
|
||||||
|
>this.foo : Symbol(C.foo, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 16))
|
||||||
|
>this : Symbol(C, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 0, 0))
|
||||||
|
>foo : Symbol(C.foo, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 16))
|
||||||
|
|
||||||
|
quiz = this.bar // ok
|
||||||
|
>quiz : Symbol(C.quiz, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 2, 18))
|
||||||
|
>this.bar : Symbol(C.bar, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 1, 18))
|
||||||
|
>this : Symbol(C, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 0, 0))
|
||||||
|
>bar : Symbol(C.bar, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 1, 18))
|
||||||
|
|
||||||
|
m1() {
|
||||||
|
>m1 : Symbol(C.m1, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 3, 19))
|
||||||
|
|
||||||
|
this.foo // ok
|
||||||
|
>this.foo : Symbol(C.foo, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 16))
|
||||||
|
>this : Symbol(C, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 0, 0))
|
||||||
|
>foo : Symbol(C.foo, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 16))
|
||||||
|
}
|
||||||
|
constructor(private foo: string) {}
|
||||||
|
>foo : Symbol(C.foo, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 16))
|
||||||
|
|
||||||
|
quim = this.baz // should error
|
||||||
|
>quim : Symbol(C.quim, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 39))
|
||||||
|
>this.baz : Symbol(C.baz, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 8, 19))
|
||||||
|
>this : Symbol(C, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 0, 0))
|
||||||
|
>baz : Symbol(C.baz, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 8, 19))
|
||||||
|
|
||||||
|
baz = this.foo; // should error
|
||||||
|
>baz : Symbol(C.baz, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 8, 19))
|
||||||
|
>this.foo : Symbol(C.foo, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 16))
|
||||||
|
>this : Symbol(C, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 0, 0))
|
||||||
|
>foo : Symbol(C.foo, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 16))
|
||||||
|
|
||||||
|
quid = this.baz // ok
|
||||||
|
>quid : Symbol(C.quid, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 9, 19))
|
||||||
|
>this.baz : Symbol(C.baz, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 8, 19))
|
||||||
|
>this : Symbol(C, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 0, 0))
|
||||||
|
>baz : Symbol(C.baz, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 8, 19))
|
||||||
|
|
||||||
|
m2() {
|
||||||
|
>m2 : Symbol(C.m2, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 10, 19))
|
||||||
|
|
||||||
|
this.foo // ok
|
||||||
|
>this.foo : Symbol(C.foo, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 16))
|
||||||
|
>this : Symbol(C, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 0, 0))
|
||||||
|
>foo : Symbol(C.foo, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 16))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
=== tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationESNext.ts ===
|
||||||
|
class C {
|
||||||
|
>C : C
|
||||||
|
|
||||||
|
qux = this.bar // should error
|
||||||
|
>qux : string
|
||||||
|
>this.bar : string
|
||||||
|
>this : this
|
||||||
|
>bar : string
|
||||||
|
|
||||||
|
bar = this.foo // should error
|
||||||
|
>bar : string
|
||||||
|
>this.foo : string
|
||||||
|
>this : this
|
||||||
|
>foo : string
|
||||||
|
|
||||||
|
quiz = this.bar // ok
|
||||||
|
>quiz : string
|
||||||
|
>this.bar : string
|
||||||
|
>this : this
|
||||||
|
>bar : string
|
||||||
|
|
||||||
|
m1() {
|
||||||
|
>m1 : () => void
|
||||||
|
|
||||||
|
this.foo // ok
|
||||||
|
>this.foo : string
|
||||||
|
>this : this
|
||||||
|
>foo : string
|
||||||
|
}
|
||||||
|
constructor(private foo: string) {}
|
||||||
|
>foo : string
|
||||||
|
|
||||||
|
quim = this.baz // should error
|
||||||
|
>quim : string
|
||||||
|
>this.baz : string
|
||||||
|
>this : this
|
||||||
|
>baz : string
|
||||||
|
|
||||||
|
baz = this.foo; // should error
|
||||||
|
>baz : string
|
||||||
|
>this.foo : string
|
||||||
|
>this : this
|
||||||
|
>foo : string
|
||||||
|
|
||||||
|
quid = this.baz // ok
|
||||||
|
>quid : string
|
||||||
|
>this.baz : string
|
||||||
|
>this : this
|
||||||
|
>baz : string
|
||||||
|
|
||||||
|
m2() {
|
||||||
|
>m2 : () => void
|
||||||
|
|
||||||
|
this.foo // ok
|
||||||
|
>this.foo : string
|
||||||
|
>this : this
|
||||||
|
>foo : string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
tests/cases/conformance/classes/propertyMemberDeclarations/defineProperty.ts(3,14): error TS2729: Property 'y' is used before its initialization.
|
||||||
|
tests/cases/conformance/classes/propertyMemberDeclarations/defineProperty.ts(10,14): error TS2729: Property 'y' is used before its initialization.
|
||||||
|
tests/cases/conformance/classes/propertyMemberDeclarations/defineProperty.ts(18,14): error TS2729: Property 'ka' is used before its initialization.
|
||||||
|
tests/cases/conformance/classes/propertyMemberDeclarations/defineProperty.ts(22,15): error TS2729: Property 'ka' is used before its initialization.
|
||||||
|
|
||||||
|
|
||||||
|
==== tests/cases/conformance/classes/propertyMemberDeclarations/defineProperty.ts (4 errors) ====
|
||||||
|
var x: "p" = "p"
|
||||||
|
class A {
|
||||||
|
a = this.y
|
||||||
|
~
|
||||||
|
!!! error TS2729: Property 'y' is used before its initialization.
|
||||||
|
!!! related TS2728 tests/cases/conformance/classes/propertyMemberDeclarations/defineProperty.ts:9:17: 'y' is declared here.
|
||||||
|
b
|
||||||
|
public c;
|
||||||
|
["computed"] = 13
|
||||||
|
;[x] = 14
|
||||||
|
m() { }
|
||||||
|
constructor(public readonly y: number) { }
|
||||||
|
z = this.y
|
||||||
|
~
|
||||||
|
!!! error TS2729: Property 'y' is used before its initialization.
|
||||||
|
!!! related TS2728 tests/cases/conformance/classes/propertyMemberDeclarations/defineProperty.ts:9:17: 'y' is declared here.
|
||||||
|
declare notEmitted;
|
||||||
|
}
|
||||||
|
class B {
|
||||||
|
public a;
|
||||||
|
}
|
||||||
|
class C extends B {
|
||||||
|
declare public a;
|
||||||
|
z = this.ka
|
||||||
|
~~
|
||||||
|
!!! error TS2729: Property 'ka' is used before its initialization.
|
||||||
|
!!! related TS2728 tests/cases/conformance/classes/propertyMemberDeclarations/defineProperty.ts:19:17: 'ka' is declared here.
|
||||||
|
constructor(public ka: number) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
ki = this.ka
|
||||||
|
~~
|
||||||
|
!!! error TS2729: Property 'ka' is used before its initialization.
|
||||||
|
!!! related TS2728 tests/cases/conformance/classes/propertyMemberDeclarations/defineProperty.ts:19:17: 'ka' is declared here.
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
// @useDefineForClassFields: true
|
||||||
|
// @target: esnext
|
||||||
|
class C {
|
||||||
|
qux = this.bar // should error
|
||||||
|
bar = this.foo // should error
|
||||||
|
quiz = this.bar // ok
|
||||||
|
m1() {
|
||||||
|
this.foo // ok
|
||||||
|
}
|
||||||
|
constructor(private foo: string) {}
|
||||||
|
quim = this.baz // should error
|
||||||
|
baz = this.foo; // should error
|
||||||
|
quid = this.baz // ok
|
||||||
|
m2() {
|
||||||
|
this.foo // ok
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue