diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 58ac3bc52c0..61c2b3f22a2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14830,11 +14830,7 @@ namespace ts { // where this references the constructor function object of a derived class, // a super property access is permitted and must specify a public static member function of the base class. if (languageVersion < ScriptTarget.ES2015) { - const hasNonMethodDeclaration = forEachProperty(prop, p => { - const propKind = getDeclarationKindFromSymbol(p); - return propKind !== SyntaxKind.MethodDeclaration && propKind !== SyntaxKind.MethodSignature; - }); - if (hasNonMethodDeclaration) { + if (symbolHasNonMethodDeclaration(prop)) { error(errorNode, Diagnostics.Only_public_and_protected_methods_of_the_base_class_are_accessible_via_the_super_keyword); return false; } @@ -14849,6 +14845,16 @@ namespace ts { } } + // Referencing Abstract Properties within Constructors is not allowed + if ((flags & ModifierFlags.Abstract) && symbolHasNonMethodDeclaration(prop)) { + const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(getParentOfSymbol(prop)); + + if (declaringClassDeclaration && isNodeWithinConstructor(node, declaringClassDeclaration)) { + error(errorNode, Diagnostics.Abstract_property_0_in_class_1_cannot_be_accessed_in_the_constructor, symbolToString(prop), typeToString(getDeclaringClass(prop))); + return false; + } + } + // Public properties are otherwise accessible. if (!(flags & ModifierFlags.NonPublicAccessibilityModifier)) { return true; @@ -14900,6 +14906,13 @@ namespace ts { return true; } + function symbolHasNonMethodDeclaration(symbol: Symbol) { + return forEachProperty(symbol, prop => { + const propKind = getDeclarationKindFromSymbol(prop); + return propKind !== SyntaxKind.MethodDeclaration && propKind !== SyntaxKind.MethodSignature; + }); + } + function checkNonNullExpression(node: Expression | QualifiedName) { return checkNonNullType(checkExpression(node), node); } @@ -23143,6 +23156,19 @@ namespace ts { return result; } + function isNodeWithinConstructor(node: Node, classDeclaration: ClassLikeDeclaration) { + return findAncestor(node, element => { + if (isConstructorDeclaration(element) && nodeIsPresent(element.body)) { + return true; + } + else if (element === classDeclaration || isFunctionLikeDeclaration(element)) { + return "quit"; + } + + return false; + }); + } + function isNodeWithinClass(node: Node, classDeclaration: ClassLikeDeclaration) { return !!forEachEnclosingClass(node, n => n === classDeclaration); } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index f3d6d4fcc47..9b220a880b0 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2220,6 +2220,10 @@ "category": "Error", "code": 2714 }, + "Abstract property '{0}' in class '{1}' cannot be accessed in the constructor.": { + "category": "Error", + "code": 2715 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", diff --git a/tests/baselines/reference/abstractPropertyInConstructor.errors.txt b/tests/baselines/reference/abstractPropertyInConstructor.errors.txt new file mode 100644 index 00000000000..461dd713d3b --- /dev/null +++ b/tests/baselines/reference/abstractPropertyInConstructor.errors.txt @@ -0,0 +1,37 @@ +tests/cases/compiler/abstractPropertyInConstructor.ts(4,24): error TS2715: Abstract property 'prop' in class 'AbstractClass' cannot be accessed in the constructor. +tests/cases/compiler/abstractPropertyInConstructor.ts(7,18): error TS2715: Abstract property 'prop' in class 'AbstractClass' cannot be accessed in the constructor. +tests/cases/compiler/abstractPropertyInConstructor.ts(9,14): error TS2715: Abstract property 'cb' in class 'AbstractClass' cannot be accessed in the constructor. + + +==== tests/cases/compiler/abstractPropertyInConstructor.ts (3 errors) ==== + abstract class AbstractClass { + constructor(str: string) { + this.method(parseInt(str)); + let val = this.prop.toLowerCase(); + ~~~~ +!!! error TS2715: Abstract property 'prop' in class 'AbstractClass' cannot be accessed in the constructor. + + if (!str) { + this.prop = "Hello World"; + ~~~~ +!!! error TS2715: Abstract property 'prop' in class 'AbstractClass' cannot be accessed in the constructor. + } + this.cb(str); + ~~ +!!! error TS2715: Abstract property 'cb' in class 'AbstractClass' cannot be accessed in the constructor. + + const innerFunction = () => { + return this.prop; + } + } + + abstract prop: string; + abstract cb: (s: string) => void; + + abstract method(num: number): void; + + method2() { + this.prop = this.prop + "!"; + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/abstractPropertyInConstructor.js b/tests/baselines/reference/abstractPropertyInConstructor.js new file mode 100644 index 00000000000..18a2937a191 --- /dev/null +++ b/tests/baselines/reference/abstractPropertyInConstructor.js @@ -0,0 +1,46 @@ +//// [abstractPropertyInConstructor.ts] +abstract class AbstractClass { + constructor(str: string) { + this.method(parseInt(str)); + let val = this.prop.toLowerCase(); + + if (!str) { + this.prop = "Hello World"; + } + this.cb(str); + + const innerFunction = () => { + return this.prop; + } + } + + abstract prop: string; + abstract cb: (s: string) => void; + + abstract method(num: number): void; + + method2() { + this.prop = this.prop + "!"; + } +} + + +//// [abstractPropertyInConstructor.js] +var AbstractClass = /** @class */ (function () { + function AbstractClass(str) { + var _this = this; + this.method(parseInt(str)); + var val = this.prop.toLowerCase(); + if (!str) { + this.prop = "Hello World"; + } + this.cb(str); + var innerFunction = function () { + return _this.prop; + }; + } + AbstractClass.prototype.method2 = function () { + this.prop = this.prop + "!"; + }; + return AbstractClass; +}()); diff --git a/tests/baselines/reference/abstractPropertyInConstructor.symbols b/tests/baselines/reference/abstractPropertyInConstructor.symbols new file mode 100644 index 00000000000..0d542ffb0a8 --- /dev/null +++ b/tests/baselines/reference/abstractPropertyInConstructor.symbols @@ -0,0 +1,70 @@ +=== tests/cases/compiler/abstractPropertyInConstructor.ts === +abstract class AbstractClass { +>AbstractClass : Symbol(AbstractClass, Decl(abstractPropertyInConstructor.ts, 0, 0)) + + constructor(str: string) { +>str : Symbol(str, Decl(abstractPropertyInConstructor.ts, 1, 16)) + + this.method(parseInt(str)); +>this.method : Symbol(AbstractClass.method, Decl(abstractPropertyInConstructor.ts, 16, 37)) +>this : Symbol(AbstractClass, Decl(abstractPropertyInConstructor.ts, 0, 0)) +>method : Symbol(AbstractClass.method, Decl(abstractPropertyInConstructor.ts, 16, 37)) +>parseInt : Symbol(parseInt, Decl(lib.d.ts, --, --)) +>str : Symbol(str, Decl(abstractPropertyInConstructor.ts, 1, 16)) + + let val = this.prop.toLowerCase(); +>val : Symbol(val, Decl(abstractPropertyInConstructor.ts, 3, 11)) +>this.prop.toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, --, --)) +>this.prop : Symbol(AbstractClass.prop, Decl(abstractPropertyInConstructor.ts, 13, 5)) +>this : Symbol(AbstractClass, Decl(abstractPropertyInConstructor.ts, 0, 0)) +>prop : Symbol(AbstractClass.prop, Decl(abstractPropertyInConstructor.ts, 13, 5)) +>toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, --, --)) + + if (!str) { +>str : Symbol(str, Decl(abstractPropertyInConstructor.ts, 1, 16)) + + this.prop = "Hello World"; +>this.prop : Symbol(AbstractClass.prop, Decl(abstractPropertyInConstructor.ts, 13, 5)) +>this : Symbol(AbstractClass, Decl(abstractPropertyInConstructor.ts, 0, 0)) +>prop : Symbol(AbstractClass.prop, Decl(abstractPropertyInConstructor.ts, 13, 5)) + } + this.cb(str); +>this.cb : Symbol(AbstractClass.cb, Decl(abstractPropertyInConstructor.ts, 15, 26)) +>this : Symbol(AbstractClass, Decl(abstractPropertyInConstructor.ts, 0, 0)) +>cb : Symbol(AbstractClass.cb, Decl(abstractPropertyInConstructor.ts, 15, 26)) +>str : Symbol(str, Decl(abstractPropertyInConstructor.ts, 1, 16)) + + const innerFunction = () => { +>innerFunction : Symbol(innerFunction, Decl(abstractPropertyInConstructor.ts, 10, 13)) + + return this.prop; +>this.prop : Symbol(AbstractClass.prop, Decl(abstractPropertyInConstructor.ts, 13, 5)) +>this : Symbol(AbstractClass, Decl(abstractPropertyInConstructor.ts, 0, 0)) +>prop : Symbol(AbstractClass.prop, Decl(abstractPropertyInConstructor.ts, 13, 5)) + } + } + + abstract prop: string; +>prop : Symbol(AbstractClass.prop, Decl(abstractPropertyInConstructor.ts, 13, 5)) + + abstract cb: (s: string) => void; +>cb : Symbol(AbstractClass.cb, Decl(abstractPropertyInConstructor.ts, 15, 26)) +>s : Symbol(s, Decl(abstractPropertyInConstructor.ts, 16, 18)) + + abstract method(num: number): void; +>method : Symbol(AbstractClass.method, Decl(abstractPropertyInConstructor.ts, 16, 37)) +>num : Symbol(num, Decl(abstractPropertyInConstructor.ts, 18, 20)) + + method2() { +>method2 : Symbol(AbstractClass.method2, Decl(abstractPropertyInConstructor.ts, 18, 39)) + + this.prop = this.prop + "!"; +>this.prop : Symbol(AbstractClass.prop, Decl(abstractPropertyInConstructor.ts, 13, 5)) +>this : Symbol(AbstractClass, Decl(abstractPropertyInConstructor.ts, 0, 0)) +>prop : Symbol(AbstractClass.prop, Decl(abstractPropertyInConstructor.ts, 13, 5)) +>this.prop : Symbol(AbstractClass.prop, Decl(abstractPropertyInConstructor.ts, 13, 5)) +>this : Symbol(AbstractClass, Decl(abstractPropertyInConstructor.ts, 0, 0)) +>prop : Symbol(AbstractClass.prop, Decl(abstractPropertyInConstructor.ts, 13, 5)) + } +} + diff --git a/tests/baselines/reference/abstractPropertyInConstructor.types b/tests/baselines/reference/abstractPropertyInConstructor.types new file mode 100644 index 00000000000..0ffb5f1bdfd --- /dev/null +++ b/tests/baselines/reference/abstractPropertyInConstructor.types @@ -0,0 +1,81 @@ +=== tests/cases/compiler/abstractPropertyInConstructor.ts === +abstract class AbstractClass { +>AbstractClass : AbstractClass + + constructor(str: string) { +>str : string + + this.method(parseInt(str)); +>this.method(parseInt(str)) : void +>this.method : (num: number) => void +>this : this +>method : (num: number) => void +>parseInt(str) : number +>parseInt : (s: string, radix?: number) => number +>str : string + + let val = this.prop.toLowerCase(); +>val : string +>this.prop.toLowerCase() : string +>this.prop.toLowerCase : () => string +>this.prop : string +>this : this +>prop : string +>toLowerCase : () => string + + if (!str) { +>!str : boolean +>str : string + + this.prop = "Hello World"; +>this.prop = "Hello World" : "Hello World" +>this.prop : string +>this : this +>prop : string +>"Hello World" : "Hello World" + } + this.cb(str); +>this.cb(str) : void +>this.cb : (s: string) => void +>this : this +>cb : (s: string) => void +>str : string + + const innerFunction = () => { +>innerFunction : () => string +>() => { return this.prop; } : () => string + + return this.prop; +>this.prop : string +>this : this +>prop : string + } + } + + abstract prop: string; +>prop : string + + abstract cb: (s: string) => void; +>cb : (s: string) => void +>s : string + + abstract method(num: number): void; +>method : (num: number) => void +>num : number + + method2() { +>method2 : () => void + + this.prop = this.prop + "!"; +>this.prop = this.prop + "!" : string +>this.prop : string +>this : this +>prop : string +>this.prop + "!" : string +>this.prop : string +>this : this +>prop : string +>"!" : "!" + } +} + diff --git a/tests/cases/compiler/abstractPropertyInConstructor.ts b/tests/cases/compiler/abstractPropertyInConstructor.ts new file mode 100644 index 00000000000..457fdb473b1 --- /dev/null +++ b/tests/cases/compiler/abstractPropertyInConstructor.ts @@ -0,0 +1,24 @@ +abstract class AbstractClass { + constructor(str: string) { + this.method(parseInt(str)); + let val = this.prop.toLowerCase(); + + if (!str) { + this.prop = "Hello World"; + } + this.cb(str); + + const innerFunction = () => { + return this.prop; + } + } + + abstract prop: string; + abstract cb: (s: string) => void; + + abstract method(num: number): void; + + method2() { + this.prop = this.prop + "!"; + } +}