diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3ac6cc094fe7f..b0cdd3c2e529f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1387,8 +1387,10 @@ namespace ts { return !findAncestor(usage, n => isComputedPropertyName(n) && n.parent.parent === declaration); } else if (isPropertyDeclaration(declaration)) { + // still might be illegal if the property has no initializer. // still might be illegal if a self-referencing property initializer (eg private x = this.x) - return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ false); + return !isUninitializedPropertyReferencedWithinDeclaration(declaration, usage) + && !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ false); } else if (isParameterPropertyDeclaration(declaration, declaration.parent)) { // foo = this.bar is illegal in esnext+useDefineForClassFields when bar is a parameter property @@ -1486,6 +1488,36 @@ namespace ts { }); } + function isUninitializedPropertyReferencedWithinDeclaration(declaration: PropertyDeclaration, usage: Node) { + if (!strictNullChecks || !strictPropertyInitialization || declaration.flags & NodeFlags.Ambient) { + return; + } + const ancestor = findAncestor(usage, (node: Node) => { + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.Block: + return "quit"; + case SyntaxKind.PropertyDeclaration: + return true; + default: + return isStatement(node) ? "quit" : false; + } + }); + + if (!ancestor || ancestor.parent !== declaration.parent) { + return false; + } + + if (!isInstancePropertyWithoutInitializer(declaration)) { + return false; + } + + const type = getTypeOfSymbol(getSymbolOfNode(declaration)); + return !(type.flags & TypeFlags.AnyOrUnknown || getFalsyFlags(type) & TypeFlags.Undefined); + } + /** stopAtAnyPropertyDeclaration is used for detecting ES-standard class field use-before-def errors */ function isPropertyImmediatelyReferencedWithinDeclaration(declaration: PropertyDeclaration | ParameterPropertyDeclaration, usage: Node, stopAtAnyPropertyDeclaration: boolean) { // always legal if usage is after declaration diff --git a/tests/baselines/reference/initializerWithThisPropertyAccess.errors.txt b/tests/baselines/reference/initializerWithThisPropertyAccess.errors.txt index 0e9502e7cb67b..6ff1d505a4285 100644 --- a/tests/baselines/reference/initializerWithThisPropertyAccess.errors.txt +++ b/tests/baselines/reference/initializerWithThisPropertyAccess.errors.txt @@ -1,8 +1,9 @@ tests/cases/compiler/initializerWithThisPropertyAccess.ts(3,14): error TS2729: Property 'a' is used before its initialization. +tests/cases/compiler/initializerWithThisPropertyAccess.ts(5,19): error TS2729: Property 'a' is used before its initialization. tests/cases/compiler/initializerWithThisPropertyAccess.ts(24,29): error TS2729: Property 'bar' is used before its initialization. -==== tests/cases/compiler/initializerWithThisPropertyAccess.ts (2 errors) ==== +==== tests/cases/compiler/initializerWithThisPropertyAccess.ts (3 errors) ==== class A { a: number; b = this.a; // Error @@ -11,6 +12,9 @@ tests/cases/compiler/initializerWithThisPropertyAccess.ts(24,29): error TS2729: !!! related TS2728 tests/cases/compiler/initializerWithThisPropertyAccess.ts:2:5: 'a' is declared here. c = () => this.a; d = (new A()).a; + ~ +!!! error TS2729: Property 'a' is used before its initialization. +!!! related TS2728 tests/cases/compiler/initializerWithThisPropertyAccess.ts:2:5: 'a' is declared here. constructor() { this.a = 1; } diff --git a/tests/baselines/reference/useBeforeDeclaration_propertyDeclaration.errors.txt b/tests/baselines/reference/useBeforeDeclaration_propertyDeclaration.errors.txt new file mode 100644 index 0000000000000..627164835198c --- /dev/null +++ b/tests/baselines/reference/useBeforeDeclaration_propertyDeclaration.errors.txt @@ -0,0 +1,15 @@ +tests/cases/compiler/useBeforeDeclaration_propertyDeclaration.ts(7,21): error TS2729: Property 'num' is used before its initialization. + + +==== tests/cases/compiler/useBeforeDeclaration_propertyDeclaration.ts (1 errors) ==== + class A { + num: number; + constructor(num:number) { + this.num = num; + } + + computed = this.num * 10; + ~~~ +!!! error TS2729: Property 'num' is used before its initialization. +!!! related TS2728 tests/cases/compiler/useBeforeDeclaration_propertyDeclaration.ts:2:5: 'num' is declared here. + } \ No newline at end of file diff --git a/tests/baselines/reference/useBeforeDeclaration_propertyDeclaration.js b/tests/baselines/reference/useBeforeDeclaration_propertyDeclaration.js new file mode 100644 index 0000000000000..3dc4e1dc28f5f --- /dev/null +++ b/tests/baselines/reference/useBeforeDeclaration_propertyDeclaration.js @@ -0,0 +1,19 @@ +//// [useBeforeDeclaration_propertyDeclaration.ts] +class A { + num: number; + constructor(num:number) { + this.num = num; + } + + computed = this.num * 10; +} + +//// [useBeforeDeclaration_propertyDeclaration.js] +"use strict"; +var A = /** @class */ (function () { + function A(num) { + this.computed = this.num * 10; + this.num = num; + } + return A; +}()); diff --git a/tests/baselines/reference/useBeforeDeclaration_propertyDeclaration.symbols b/tests/baselines/reference/useBeforeDeclaration_propertyDeclaration.symbols new file mode 100644 index 0000000000000..e4f590c4db8a7 --- /dev/null +++ b/tests/baselines/reference/useBeforeDeclaration_propertyDeclaration.symbols @@ -0,0 +1,23 @@ +=== tests/cases/compiler/useBeforeDeclaration_propertyDeclaration.ts === +class A { +>A : Symbol(A, Decl(useBeforeDeclaration_propertyDeclaration.ts, 0, 0)) + + num: number; +>num : Symbol(A.num, Decl(useBeforeDeclaration_propertyDeclaration.ts, 0, 9)) + + constructor(num:number) { +>num : Symbol(num, Decl(useBeforeDeclaration_propertyDeclaration.ts, 2, 16)) + + this.num = num; +>this.num : Symbol(A.num, Decl(useBeforeDeclaration_propertyDeclaration.ts, 0, 9)) +>this : Symbol(A, Decl(useBeforeDeclaration_propertyDeclaration.ts, 0, 0)) +>num : Symbol(A.num, Decl(useBeforeDeclaration_propertyDeclaration.ts, 0, 9)) +>num : Symbol(num, Decl(useBeforeDeclaration_propertyDeclaration.ts, 2, 16)) + } + + computed = this.num * 10; +>computed : Symbol(A.computed, Decl(useBeforeDeclaration_propertyDeclaration.ts, 4, 5)) +>this.num : Symbol(A.num, Decl(useBeforeDeclaration_propertyDeclaration.ts, 0, 9)) +>this : Symbol(A, Decl(useBeforeDeclaration_propertyDeclaration.ts, 0, 0)) +>num : Symbol(A.num, Decl(useBeforeDeclaration_propertyDeclaration.ts, 0, 9)) +} diff --git a/tests/baselines/reference/useBeforeDeclaration_propertyDeclaration.types b/tests/baselines/reference/useBeforeDeclaration_propertyDeclaration.types new file mode 100644 index 0000000000000..d30076c86d8fe --- /dev/null +++ b/tests/baselines/reference/useBeforeDeclaration_propertyDeclaration.types @@ -0,0 +1,26 @@ +=== tests/cases/compiler/useBeforeDeclaration_propertyDeclaration.ts === +class A { +>A : A + + num: number; +>num : number + + constructor(num:number) { +>num : number + + this.num = num; +>this.num = num : number +>this.num : number +>this : this +>num : number +>num : number + } + + computed = this.num * 10; +>computed : number +>this.num * 10 : number +>this.num : number +>this : this +>num : number +>10 : 10 +} diff --git a/tests/cases/compiler/useBeforeDeclaration_propertyDeclaration.ts b/tests/cases/compiler/useBeforeDeclaration_propertyDeclaration.ts new file mode 100644 index 0000000000000..05c9b4ec0deb5 --- /dev/null +++ b/tests/cases/compiler/useBeforeDeclaration_propertyDeclaration.ts @@ -0,0 +1,9 @@ +// @strict: true +class A { + num: number; + constructor(num:number) { + this.num = num; + } + + computed = this.num * 10; +} \ No newline at end of file