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
This commit is contained in:
Nathan Shively-Sanders 2019-04-05 09:06:36 -07:00 committed by GitHub
commit 0b3b4ea127
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 478 additions and 1 deletions

View file

@ -1130,6 +1130,10 @@ namespace ts {
// still might be illegal if the usage is within a computed property name in the class (eg class A { static p = "a"; [A.p]() {} })
return !findAncestor(usage, n => isComputedPropertyName(n) && n.parent.parent === declaration);
}
else if (isPropertyDeclaration(declaration)) {
// still might be illegal if a self-referencing property initializer (eg private x = this.x)
return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage);
}
return true;
}
@ -1204,6 +1208,40 @@ namespace ts {
return false;
});
}
function isPropertyImmediatelyReferencedWithinDeclaration(declaration: PropertyDeclaration, usage: Node) {
// always legal if usage is after declaration
if (usage.end > declaration.end) {
return false;
}
// still might be legal if usage is deferred (e.g. x: any = () => this.x)
// otherwise illegal if immediately referenced within the declaration (e.g. x: any = this.x)
const ancestorChangingReferenceScope = findAncestor(usage, (node: Node) => {
if (node === declaration) {
return "quit";
}
switch (node.kind) {
case SyntaxKind.ArrowFunction:
case SyntaxKind.PropertyDeclaration:
return true;
case SyntaxKind.Block:
switch (node.parent.kind) {
case SyntaxKind.GetAccessor:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.SetAccessor:
return true;
default:
return false;
}
default:
return false;
}
});
return ancestorChangingReferenceScope === undefined;
}
}
/**

View file

@ -0,0 +1,66 @@
tests/cases/compiler/classUsedBeforeInitializedVariables.ts(4,15): error TS2729: Property 'p4' is used before its initialization.
tests/cases/compiler/classUsedBeforeInitializedVariables.ts(7,34): error TS2729: Property 'directlyAssigned' is used before its initialization.
tests/cases/compiler/classUsedBeforeInitializedVariables.ts(16,15): error TS2729: Property 'withinObjectLiteral' is used before its initialization.
tests/cases/compiler/classUsedBeforeInitializedVariables.ts(20,19): error TS2729: Property 'withinObjectLiteralGetterName' is used before its initialization.
tests/cases/compiler/classUsedBeforeInitializedVariables.ts(26,19): error TS2729: Property 'withinObjectLiteralSetterName' is used before its initialization.
tests/cases/compiler/classUsedBeforeInitializedVariables.ts(29,64): error TS2729: Property 'withinClassDeclarationExtension' is used before its initialization.
==== tests/cases/compiler/classUsedBeforeInitializedVariables.ts (6 errors) ====
class Test {
p1 = 0;
p2 = this.p1;
p3 = this.p4;
~~
!!! error TS2729: Property 'p4' is used before its initialization.
!!! related TS2728 tests/cases/compiler/classUsedBeforeInitializedVariables.ts:5:5: 'p4' is declared here.
p4 = 0;
directlyAssigned: any = this.directlyAssigned;
~~~~~~~~~~~~~~~~
!!! error TS2729: Property 'directlyAssigned' is used before its initialization.
!!! related TS2728 tests/cases/compiler/classUsedBeforeInitializedVariables.ts:7:5: 'directlyAssigned' is declared here.
withinArrowFunction: any = () => this.withinArrowFunction;
withinFunction: any = function () {
return this.withinFunction;
};
withinObjectLiteral: any = {
[this.withinObjectLiteral]: true,
~~~~~~~~~~~~~~~~~~~
!!! error TS2729: Property 'withinObjectLiteral' is used before its initialization.
!!! related TS2728 tests/cases/compiler/classUsedBeforeInitializedVariables.ts:15:5: 'withinObjectLiteral' is declared here.
};
withinObjectLiteralGetterName: any = {
get [this.withinObjectLiteralGetterName]() {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2729: Property 'withinObjectLiteralGetterName' is used before its initialization.
!!! related TS2728 tests/cases/compiler/classUsedBeforeInitializedVariables.ts:19:5: 'withinObjectLiteralGetterName' is declared here.
return true;
}
};
withinObjectLiteralSetterName: any = {
set [this.withinObjectLiteralSetterName](_: any) {}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2729: Property 'withinObjectLiteralSetterName' is used before its initialization.
!!! related TS2728 tests/cases/compiler/classUsedBeforeInitializedVariables.ts:25:5: 'withinObjectLiteralSetterName' is declared here.
};
withinClassDeclarationExtension: any = (class extends this.withinClassDeclarationExtension { });
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2729: Property 'withinClassDeclarationExtension' is used before its initialization.
!!! related TS2728 tests/cases/compiler/classUsedBeforeInitializedVariables.ts:29:5: 'withinClassDeclarationExtension' is declared here.
// These error cases are ignored (not checked by control flow analysis)
assignedByArrowFunction: any = (() => this.assignedByFunction)();
assignedByFunction: any = (function () {
return this.assignedByFunction;
})();
}

View file

@ -0,0 +1,102 @@
//// [classUsedBeforeInitializedVariables.ts]
class Test {
p1 = 0;
p2 = this.p1;
p3 = this.p4;
p4 = 0;
directlyAssigned: any = this.directlyAssigned;
withinArrowFunction: any = () => this.withinArrowFunction;
withinFunction: any = function () {
return this.withinFunction;
};
withinObjectLiteral: any = {
[this.withinObjectLiteral]: true,
};
withinObjectLiteralGetterName: any = {
get [this.withinObjectLiteralGetterName]() {
return true;
}
};
withinObjectLiteralSetterName: any = {
set [this.withinObjectLiteralSetterName](_: any) {}
};
withinClassDeclarationExtension: any = (class extends this.withinClassDeclarationExtension { });
// These error cases are ignored (not checked by control flow analysis)
assignedByArrowFunction: any = (() => this.assignedByFunction)();
assignedByFunction: any = (function () {
return this.assignedByFunction;
})();
}
//// [classUsedBeforeInitializedVariables.js]
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var Test = /** @class */ (function () {
function Test() {
var _a, _b, _c;
var _this = this;
this.p1 = 0;
this.p2 = this.p1;
this.p3 = this.p4;
this.p4 = 0;
this.directlyAssigned = this.directlyAssigned;
this.withinArrowFunction = function () { return _this.withinArrowFunction; };
this.withinFunction = function () {
return this.withinFunction;
};
this.withinObjectLiteral = (_a = {},
_a[this.withinObjectLiteral] = true,
_a);
this.withinObjectLiteralGetterName = (_b = {},
Object.defineProperty(_b, this.withinObjectLiteralGetterName, {
get: function () {
return true;
},
enumerable: true,
configurable: true
}),
_b);
this.withinObjectLiteralSetterName = (_c = {},
Object.defineProperty(_c, this.withinObjectLiteralSetterName, {
set: function (_) { },
enumerable: true,
configurable: true
}),
_c);
this.withinClassDeclarationExtension = (/** @class */ (function (_super) {
__extends(class_1, _super);
function class_1() {
return _super !== null && _super.apply(this, arguments) || this;
}
return class_1;
}(this.withinClassDeclarationExtension)));
// These error cases are ignored (not checked by control flow analysis)
this.assignedByArrowFunction = (function () { return _this.assignedByFunction; })();
this.assignedByFunction = (function () {
return this.assignedByFunction;
})();
}
return Test;
}());

View file

@ -0,0 +1,97 @@
=== tests/cases/compiler/classUsedBeforeInitializedVariables.ts ===
class Test {
>Test : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0))
p1 = 0;
>p1 : Symbol(Test.p1, Decl(classUsedBeforeInitializedVariables.ts, 0, 12))
p2 = this.p1;
>p2 : Symbol(Test.p2, Decl(classUsedBeforeInitializedVariables.ts, 1, 11))
>this.p1 : Symbol(Test.p1, Decl(classUsedBeforeInitializedVariables.ts, 0, 12))
>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0))
>p1 : Symbol(Test.p1, Decl(classUsedBeforeInitializedVariables.ts, 0, 12))
p3 = this.p4;
>p3 : Symbol(Test.p3, Decl(classUsedBeforeInitializedVariables.ts, 2, 17))
>this.p4 : Symbol(Test.p4, Decl(classUsedBeforeInitializedVariables.ts, 3, 17))
>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0))
>p4 : Symbol(Test.p4, Decl(classUsedBeforeInitializedVariables.ts, 3, 17))
p4 = 0;
>p4 : Symbol(Test.p4, Decl(classUsedBeforeInitializedVariables.ts, 3, 17))
directlyAssigned: any = this.directlyAssigned;
>directlyAssigned : Symbol(Test.directlyAssigned, Decl(classUsedBeforeInitializedVariables.ts, 4, 11))
>this.directlyAssigned : Symbol(Test.directlyAssigned, Decl(classUsedBeforeInitializedVariables.ts, 4, 11))
>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0))
>directlyAssigned : Symbol(Test.directlyAssigned, Decl(classUsedBeforeInitializedVariables.ts, 4, 11))
withinArrowFunction: any = () => this.withinArrowFunction;
>withinArrowFunction : Symbol(Test.withinArrowFunction, Decl(classUsedBeforeInitializedVariables.ts, 6, 50))
>this.withinArrowFunction : Symbol(Test.withinArrowFunction, Decl(classUsedBeforeInitializedVariables.ts, 6, 50))
>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0))
>withinArrowFunction : Symbol(Test.withinArrowFunction, Decl(classUsedBeforeInitializedVariables.ts, 6, 50))
withinFunction: any = function () {
>withinFunction : Symbol(Test.withinFunction, Decl(classUsedBeforeInitializedVariables.ts, 8, 62))
return this.withinFunction;
};
withinObjectLiteral: any = {
>withinObjectLiteral : Symbol(Test.withinObjectLiteral, Decl(classUsedBeforeInitializedVariables.ts, 12, 6))
[this.withinObjectLiteral]: true,
>[this.withinObjectLiteral] : Symbol([this.withinObjectLiteral], Decl(classUsedBeforeInitializedVariables.ts, 14, 32))
>this.withinObjectLiteral : Symbol(Test.withinObjectLiteral, Decl(classUsedBeforeInitializedVariables.ts, 12, 6))
>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0))
>withinObjectLiteral : Symbol(Test.withinObjectLiteral, Decl(classUsedBeforeInitializedVariables.ts, 12, 6))
};
withinObjectLiteralGetterName: any = {
>withinObjectLiteralGetterName : Symbol(Test.withinObjectLiteralGetterName, Decl(classUsedBeforeInitializedVariables.ts, 16, 6))
get [this.withinObjectLiteralGetterName]() {
>[this.withinObjectLiteralGetterName] : Symbol([this.withinObjectLiteralGetterName], Decl(classUsedBeforeInitializedVariables.ts, 18, 42))
>this.withinObjectLiteralGetterName : Symbol(Test.withinObjectLiteralGetterName, Decl(classUsedBeforeInitializedVariables.ts, 16, 6))
>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0))
>withinObjectLiteralGetterName : Symbol(Test.withinObjectLiteralGetterName, Decl(classUsedBeforeInitializedVariables.ts, 16, 6))
return true;
}
};
withinObjectLiteralSetterName: any = {
>withinObjectLiteralSetterName : Symbol(Test.withinObjectLiteralSetterName, Decl(classUsedBeforeInitializedVariables.ts, 22, 6))
set [this.withinObjectLiteralSetterName](_: any) {}
>[this.withinObjectLiteralSetterName] : Symbol([this.withinObjectLiteralSetterName], Decl(classUsedBeforeInitializedVariables.ts, 24, 42))
>this.withinObjectLiteralSetterName : Symbol(Test.withinObjectLiteralSetterName, Decl(classUsedBeforeInitializedVariables.ts, 22, 6))
>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0))
>withinObjectLiteralSetterName : Symbol(Test.withinObjectLiteralSetterName, Decl(classUsedBeforeInitializedVariables.ts, 22, 6))
>_ : Symbol(_, Decl(classUsedBeforeInitializedVariables.ts, 25, 49))
};
withinClassDeclarationExtension: any = (class extends this.withinClassDeclarationExtension { });
>withinClassDeclarationExtension : Symbol(Test.withinClassDeclarationExtension, Decl(classUsedBeforeInitializedVariables.ts, 26, 6))
>this.withinClassDeclarationExtension : Symbol(Test.withinClassDeclarationExtension, Decl(classUsedBeforeInitializedVariables.ts, 26, 6))
>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0))
>withinClassDeclarationExtension : Symbol(Test.withinClassDeclarationExtension, Decl(classUsedBeforeInitializedVariables.ts, 26, 6))
// These error cases are ignored (not checked by control flow analysis)
assignedByArrowFunction: any = (() => this.assignedByFunction)();
>assignedByArrowFunction : Symbol(Test.assignedByArrowFunction, Decl(classUsedBeforeInitializedVariables.ts, 28, 100))
>this.assignedByFunction : Symbol(Test.assignedByFunction, Decl(classUsedBeforeInitializedVariables.ts, 32, 69))
>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0))
>assignedByFunction : Symbol(Test.assignedByFunction, Decl(classUsedBeforeInitializedVariables.ts, 32, 69))
assignedByFunction: any = (function () {
>assignedByFunction : Symbol(Test.assignedByFunction, Decl(classUsedBeforeInitializedVariables.ts, 32, 69))
return this.assignedByFunction;
})();
}

View file

@ -0,0 +1,122 @@
=== tests/cases/compiler/classUsedBeforeInitializedVariables.ts ===
class Test {
>Test : Test
p1 = 0;
>p1 : number
>0 : 0
p2 = this.p1;
>p2 : number
>this.p1 : number
>this : this
>p1 : number
p3 = this.p4;
>p3 : number
>this.p4 : number
>this : this
>p4 : number
p4 = 0;
>p4 : number
>0 : 0
directlyAssigned: any = this.directlyAssigned;
>directlyAssigned : any
>this.directlyAssigned : any
>this : this
>directlyAssigned : any
withinArrowFunction: any = () => this.withinArrowFunction;
>withinArrowFunction : any
>() => this.withinArrowFunction : () => any
>this.withinArrowFunction : any
>this : this
>withinArrowFunction : any
withinFunction: any = function () {
>withinFunction : any
>function () { return this.withinFunction; } : () => any
return this.withinFunction;
>this.withinFunction : any
>this : any
>withinFunction : any
};
withinObjectLiteral: any = {
>withinObjectLiteral : any
>{ [this.withinObjectLiteral]: true, } : { [x: number]: boolean; }
[this.withinObjectLiteral]: true,
>[this.withinObjectLiteral] : boolean
>this.withinObjectLiteral : any
>this : this
>withinObjectLiteral : any
>true : true
};
withinObjectLiteralGetterName: any = {
>withinObjectLiteralGetterName : any
>{ get [this.withinObjectLiteralGetterName]() { return true; } } : { [x: number]: boolean; }
get [this.withinObjectLiteralGetterName]() {
>[this.withinObjectLiteralGetterName] : boolean
>this.withinObjectLiteralGetterName : any
>this : this
>withinObjectLiteralGetterName : any
return true;
>true : true
}
};
withinObjectLiteralSetterName: any = {
>withinObjectLiteralSetterName : any
>{ set [this.withinObjectLiteralSetterName](_: any) {} } : { [x: number]: any; }
set [this.withinObjectLiteralSetterName](_: any) {}
>[this.withinObjectLiteralSetterName] : any
>this.withinObjectLiteralSetterName : any
>this : this
>withinObjectLiteralSetterName : any
>_ : any
};
withinClassDeclarationExtension: any = (class extends this.withinClassDeclarationExtension { });
>withinClassDeclarationExtension : any
>(class extends this.withinClassDeclarationExtension { }) : typeof (Anonymous class)
>class extends this.withinClassDeclarationExtension { } : typeof (Anonymous class)
>this.withinClassDeclarationExtension : any
>this : this
>withinClassDeclarationExtension : any
// These error cases are ignored (not checked by control flow analysis)
assignedByArrowFunction: any = (() => this.assignedByFunction)();
>assignedByArrowFunction : any
>(() => this.assignedByFunction)() : any
>(() => this.assignedByFunction) : () => any
>() => this.assignedByFunction : () => any
>this.assignedByFunction : any
>this : this
>assignedByFunction : any
assignedByFunction: any = (function () {
>assignedByFunction : any
>(function () { return this.assignedByFunction; })() : any
>(function () { return this.assignedByFunction; }) : () => any
>function () { return this.assignedByFunction; } : () => any
return this.assignedByFunction;
>this.assignedByFunction : any
>this : any
>assignedByFunction : any
})();
}

View file

@ -1,4 +1,5 @@
tests/cases/conformance/types/witness/witness.ts(4,21): error TS2372: Parameter 'pInit' cannot be referenced in its initializer.
tests/cases/conformance/types/witness/witness.ts(8,14): error TS2729: Property 'x' is used before its initialization.
tests/cases/conformance/types/witness/witness.ts(28,12): error TS2695: Left side of comma operator is unused and has no side effects.
tests/cases/conformance/types/witness/witness.ts(29,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'co1' must be of type 'any', but here has type 'number'.
tests/cases/conformance/types/witness/witness.ts(30,12): error TS2695: Left side of comma operator is unused and has no side effects.
@ -12,9 +13,11 @@ tests/cases/conformance/types/witness/witness.ts(39,5): error TS2403: Subsequent
tests/cases/conformance/types/witness/witness.ts(43,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'cnd1' must be of type 'any', but here has type 'number'.
tests/cases/conformance/types/witness/witness.ts(57,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'and1' must be of type 'any', but here has type 'string'.
tests/cases/conformance/types/witness/witness.ts(110,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'propAcc1' must be of type 'any', but here has type '{ m: any; }'.
tests/cases/conformance/types/witness/witness.ts(121,14): error TS2729: Property 'n' is used before its initialization.
tests/cases/conformance/types/witness/witness.ts(128,19): error TS2729: Property 'q' is used before its initialization.
==== tests/cases/conformance/types/witness/witness.ts (14 errors) ====
==== tests/cases/conformance/types/witness/witness.ts (17 errors) ====
// Initializers
var varInit = varInit; // any
var pInit: any;
@ -25,6 +28,9 @@ tests/cases/conformance/types/witness/witness.ts(110,5): error TS2403: Subsequen
}
class InitClass {
x = this.x;
~
!!! error TS2729: Property 'x' is used before its initialization.
!!! related TS2728 tests/cases/conformance/types/witness/witness.ts:8:5: 'x' is declared here.
fn() {
var y = this.x;
var y: any;
@ -164,6 +170,9 @@ tests/cases/conformance/types/witness/witness.ts(110,5): error TS2403: Subsequen
// Property access of class instance type
class C2 {
n = this.n; // n: any
~
!!! error TS2729: Property 'n' is used before its initialization.
!!! related TS2728 tests/cases/conformance/types/witness/witness.ts:121:5: 'n' is declared here.
}
var c2inst = new C2().n;
var c2inst: any;
@ -171,6 +180,9 @@ tests/cases/conformance/types/witness/witness.ts(110,5): error TS2403: Subsequen
// Constructor function property access
class C3 {
static q = C3.q;
~
!!! error TS2729: Property 'q' is used before its initialization.
!!! related TS2728 tests/cases/conformance/types/witness/witness.ts:128:12: 'q' is declared here.
}
var qq = C3.q;
var qq: any;

View file

@ -0,0 +1,40 @@
// @target: es5
class Test {
p1 = 0;
p2 = this.p1;
p3 = this.p4;
p4 = 0;
directlyAssigned: any = this.directlyAssigned;
withinArrowFunction: any = () => this.withinArrowFunction;
withinFunction: any = function () {
return this.withinFunction;
};
withinObjectLiteral: any = {
[this.withinObjectLiteral]: true,
};
withinObjectLiteralGetterName: any = {
get [this.withinObjectLiteralGetterName]() {
return true;
}
};
withinObjectLiteralSetterName: any = {
set [this.withinObjectLiteralSetterName](_: any) {}
};
withinClassDeclarationExtension: any = (class extends this.withinClassDeclarationExtension { });
// These error cases are ignored (not checked by control flow analysis)
assignedByArrowFunction: any = (() => this.assignedByFunction)();
assignedByFunction: any = (function () {
return this.assignedByFunction;
})();
}