Skip to content

Added error for class properties used within their own declaration #29395

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
})();
}

102 changes: 102 additions & 0 deletions tests/baselines/reference/classUsedBeforeInitializedVariables.js
Original file line number Diff line number Diff line change
@@ -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;
}());
Original file line number Diff line number Diff line change
@@ -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;
})();
}

Loading