From f2acd7204e5989df0f165c82a46b42f910ceecdf Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 4 Sep 2019 14:02:20 -0700 Subject: [PATCH] Add use-before-def error for uninitialized property --- src/compiler/checker.ts | 34 ++++++++++++++++++- ...Declaration_propertyDeclaration.errors.txt | 15 ++++++++ ...seBeforeDeclaration_propertyDeclaration.js | 19 +++++++++++ ...oreDeclaration_propertyDeclaration.symbols | 23 +++++++++++++ ...eforeDeclaration_propertyDeclaration.types | 26 ++++++++++++++ ...seBeforeDeclaration_propertyDeclaration.ts | 9 +++++ 6 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/useBeforeDeclaration_propertyDeclaration.errors.txt create mode 100644 tests/baselines/reference/useBeforeDeclaration_propertyDeclaration.js create mode 100644 tests/baselines/reference/useBeforeDeclaration_propertyDeclaration.symbols create mode 100644 tests/baselines/reference/useBeforeDeclaration_propertyDeclaration.types create mode 100644 tests/cases/compiler/useBeforeDeclaration_propertyDeclaration.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cfad47b65336f..5d0fcd45d19b5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1297,8 +1297,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); + return !isUninitializedPropertyReferencedWithinDeclaration(declaration, usage) + && !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage); } return true; } @@ -1375,6 +1377,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); + } + function isPropertyImmediatelyReferencedWithinDeclaration(declaration: PropertyDeclaration, usage: Node) { // always legal if usage is after declaration if (usage.end > declaration.end) { 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