From 38da7c600c83e7b31193a62495239a0fe478cb67 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 2 Apr 2021 13:08:10 -0400 Subject: [PATCH] Disabled 'used before initialization' error for optional properties (#43100) * Disabled 'used before initialization' error for optional properties * Expanded tests to include code snippet from issue --- src/compiler/checker.ts | 5 ++ ...sUsedBeforeInitializedVariables.errors.txt | 28 ++++--- .../classUsedBeforeInitializedVariables.js | 12 +++ ...lassUsedBeforeInitializedVariables.symbols | 79 ++++++++++++------- .../classUsedBeforeInitializedVariables.types | 27 +++++++ .../classUsedBeforeInitializedVariables.ts | 8 ++ 6 files changed, 121 insertions(+), 38 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4201853d856..7c8062eacd9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11936,6 +11936,10 @@ namespace ts { return false; } + function isOptionalPropertyDeclaration(node: Declaration) { + return isPropertyDeclaration(node) && node.questionToken; + } + function isOptionalJSDocPropertyLikeTag(node: Node): node is JSDocPropertyLikeTag { if (!isJSDocPropertyLikeTag(node)) { return false; @@ -27115,6 +27119,7 @@ namespace ts { let diagnosticMessage; const declarationName = idText(right); if (isInPropertyInitializer(node) + && !isOptionalPropertyDeclaration(valueDeclaration) && !(isAccessExpression(node) && isAccessExpression(node.expression)) && !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right) && (compilerOptions.useDefineForClassFields || !isPropertyDeclaredInAncestorClass(prop))) { diff --git a/tests/baselines/reference/classUsedBeforeInitializedVariables.errors.txt b/tests/baselines/reference/classUsedBeforeInitializedVariables.errors.txt index 685c854039e..3c183ae3c21 100644 --- a/tests/baselines/reference/classUsedBeforeInitializedVariables.errors.txt +++ b/tests/baselines/reference/classUsedBeforeInitializedVariables.errors.txt @@ -1,9 +1,9 @@ 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(13,34): error TS2729: Property 'directlyAssigned' is used before its initialization. +tests/cases/compiler/classUsedBeforeInitializedVariables.ts(22,15): error TS2729: Property 'withinObjectLiteral' is used before its initialization. +tests/cases/compiler/classUsedBeforeInitializedVariables.ts(26,19): error TS2729: Property 'withinObjectLiteralGetterName' is used before its initialization. +tests/cases/compiler/classUsedBeforeInitializedVariables.ts(32,19): error TS2729: Property 'withinObjectLiteralSetterName' is used before its initialization. +tests/cases/compiler/classUsedBeforeInitializedVariables.ts(35,64): error TS2729: Property 'withinClassDeclarationExtension' is used before its initialization. ==== tests/cases/compiler/classUsedBeforeInitializedVariables.ts (6 errors) ==== @@ -15,11 +15,17 @@ tests/cases/compiler/classUsedBeforeInitializedVariables.ts(29,64): error TS2729 !!! error TS2729: Property 'p4' is used before its initialization. !!! related TS2728 tests/cases/compiler/classUsedBeforeInitializedVariables.ts:5:5: 'p4' is declared here. p4 = 0; + p5?: number; + + p6?: string; + p7 = { + hello: (this.p6 = "string"), + }; 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. +!!! related TS2728 tests/cases/compiler/classUsedBeforeInitializedVariables.ts:13:5: 'directlyAssigned' is declared here. withinArrowFunction: any = () => this.withinArrowFunction; @@ -31,14 +37,14 @@ tests/cases/compiler/classUsedBeforeInitializedVariables.ts(29,64): error TS2729 [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. +!!! related TS2728 tests/cases/compiler/classUsedBeforeInitializedVariables.ts:21: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. +!!! related TS2728 tests/cases/compiler/classUsedBeforeInitializedVariables.ts:25:5: 'withinObjectLiteralGetterName' is declared here. return true; } }; @@ -47,13 +53,15 @@ tests/cases/compiler/classUsedBeforeInitializedVariables.ts(29,64): error TS2729 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. +!!! related TS2728 tests/cases/compiler/classUsedBeforeInitializedVariables.ts:31: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. +!!! related TS2728 tests/cases/compiler/classUsedBeforeInitializedVariables.ts:35:5: 'withinClassDeclarationExtension' is declared here. + + fromOptional = this.p5; // These error cases are ignored (not checked by control flow analysis) diff --git a/tests/baselines/reference/classUsedBeforeInitializedVariables.js b/tests/baselines/reference/classUsedBeforeInitializedVariables.js index a1ed369e006..64b09b345af 100644 --- a/tests/baselines/reference/classUsedBeforeInitializedVariables.js +++ b/tests/baselines/reference/classUsedBeforeInitializedVariables.js @@ -4,6 +4,12 @@ class Test { p2 = this.p1; p3 = this.p4; p4 = 0; + p5?: number; + + p6?: string; + p7 = { + hello: (this.p6 = "string"), + }; directlyAssigned: any = this.directlyAssigned; @@ -29,6 +35,8 @@ class Test { withinClassDeclarationExtension: any = (class extends this.withinClassDeclarationExtension { }); + fromOptional = this.p5; + // These error cases are ignored (not checked by control flow analysis) assignedByArrowFunction: any = (() => this.assignedByFunction)(); @@ -63,6 +71,9 @@ var Test = /** @class */ (function () { this.p2 = this.p1; this.p3 = this.p4; this.p4 = 0; + this.p7 = { + hello: (this.p6 = "string"), + }; this.directlyAssigned = this.directlyAssigned; this.withinArrowFunction = function () { return _this.withinArrowFunction; }; this.withinFunction = function () { @@ -94,6 +105,7 @@ var Test = /** @class */ (function () { } return class_1; }(this.withinClassDeclarationExtension))); + this.fromOptional = this.p5; // These error cases are ignored (not checked by control flow analysis) this.assignedByArrowFunction = (function () { return _this.assignedByFunction; })(); this.assignedByFunction = (function () { diff --git a/tests/baselines/reference/classUsedBeforeInitializedVariables.symbols b/tests/baselines/reference/classUsedBeforeInitializedVariables.symbols index 4885953ed58..f51022958e6 100644 --- a/tests/baselines/reference/classUsedBeforeInitializedVariables.symbols +++ b/tests/baselines/reference/classUsedBeforeInitializedVariables.symbols @@ -20,76 +20,99 @@ class Test { 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)) + p5?: number; +>p5 : Symbol(Test.p5, Decl(classUsedBeforeInitializedVariables.ts, 4, 11)) + + p6?: string; +>p6 : Symbol(Test.p6, Decl(classUsedBeforeInitializedVariables.ts, 5, 16)) + + p7 = { +>p7 : Symbol(Test.p7, Decl(classUsedBeforeInitializedVariables.ts, 7, 16)) + + hello: (this.p6 = "string"), +>hello : Symbol(hello, Decl(classUsedBeforeInitializedVariables.ts, 8, 10)) +>this.p6 : Symbol(Test.p6, Decl(classUsedBeforeInitializedVariables.ts, 5, 16)) >this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0)) ->directlyAssigned : Symbol(Test.directlyAssigned, Decl(classUsedBeforeInitializedVariables.ts, 4, 11)) +>p6 : Symbol(Test.p6, Decl(classUsedBeforeInitializedVariables.ts, 5, 16)) + + }; + + directlyAssigned: any = this.directlyAssigned; +>directlyAssigned : Symbol(Test.directlyAssigned, Decl(classUsedBeforeInitializedVariables.ts, 10, 6)) +>this.directlyAssigned : Symbol(Test.directlyAssigned, Decl(classUsedBeforeInitializedVariables.ts, 10, 6)) +>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0)) +>directlyAssigned : Symbol(Test.directlyAssigned, Decl(classUsedBeforeInitializedVariables.ts, 10, 6)) withinArrowFunction: any = () => this.withinArrowFunction; ->withinArrowFunction : Symbol(Test.withinArrowFunction, Decl(classUsedBeforeInitializedVariables.ts, 6, 50)) ->this.withinArrowFunction : Symbol(Test.withinArrowFunction, Decl(classUsedBeforeInitializedVariables.ts, 6, 50)) +>withinArrowFunction : Symbol(Test.withinArrowFunction, Decl(classUsedBeforeInitializedVariables.ts, 12, 50)) +>this.withinArrowFunction : Symbol(Test.withinArrowFunction, Decl(classUsedBeforeInitializedVariables.ts, 12, 50)) >this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0)) ->withinArrowFunction : Symbol(Test.withinArrowFunction, Decl(classUsedBeforeInitializedVariables.ts, 6, 50)) +>withinArrowFunction : Symbol(Test.withinArrowFunction, Decl(classUsedBeforeInitializedVariables.ts, 12, 50)) withinFunction: any = function () { ->withinFunction : Symbol(Test.withinFunction, Decl(classUsedBeforeInitializedVariables.ts, 8, 62)) +>withinFunction : Symbol(Test.withinFunction, Decl(classUsedBeforeInitializedVariables.ts, 14, 62)) return this.withinFunction; }; withinObjectLiteral: any = { ->withinObjectLiteral : Symbol(Test.withinObjectLiteral, Decl(classUsedBeforeInitializedVariables.ts, 12, 6)) +>withinObjectLiteral : Symbol(Test.withinObjectLiteral, Decl(classUsedBeforeInitializedVariables.ts, 18, 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.withinObjectLiteral] : Symbol([this.withinObjectLiteral], Decl(classUsedBeforeInitializedVariables.ts, 20, 32)) +>this.withinObjectLiteral : Symbol(Test.withinObjectLiteral, Decl(classUsedBeforeInitializedVariables.ts, 18, 6)) >this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0)) ->withinObjectLiteral : Symbol(Test.withinObjectLiteral, Decl(classUsedBeforeInitializedVariables.ts, 12, 6)) +>withinObjectLiteral : Symbol(Test.withinObjectLiteral, Decl(classUsedBeforeInitializedVariables.ts, 18, 6)) }; withinObjectLiteralGetterName: any = { ->withinObjectLiteralGetterName : Symbol(Test.withinObjectLiteralGetterName, Decl(classUsedBeforeInitializedVariables.ts, 16, 6)) +>withinObjectLiteralGetterName : Symbol(Test.withinObjectLiteralGetterName, Decl(classUsedBeforeInitializedVariables.ts, 22, 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.withinObjectLiteralGetterName] : Symbol([this.withinObjectLiteralGetterName], Decl(classUsedBeforeInitializedVariables.ts, 24, 42)) +>this.withinObjectLiteralGetterName : Symbol(Test.withinObjectLiteralGetterName, Decl(classUsedBeforeInitializedVariables.ts, 22, 6)) >this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0)) ->withinObjectLiteralGetterName : Symbol(Test.withinObjectLiteralGetterName, Decl(classUsedBeforeInitializedVariables.ts, 16, 6)) +>withinObjectLiteralGetterName : Symbol(Test.withinObjectLiteralGetterName, Decl(classUsedBeforeInitializedVariables.ts, 22, 6)) return true; } }; withinObjectLiteralSetterName: any = { ->withinObjectLiteralSetterName : Symbol(Test.withinObjectLiteralSetterName, Decl(classUsedBeforeInitializedVariables.ts, 22, 6)) +>withinObjectLiteralSetterName : Symbol(Test.withinObjectLiteralSetterName, Decl(classUsedBeforeInitializedVariables.ts, 28, 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.withinObjectLiteralSetterName] : Symbol([this.withinObjectLiteralSetterName], Decl(classUsedBeforeInitializedVariables.ts, 30, 42)) +>this.withinObjectLiteralSetterName : Symbol(Test.withinObjectLiteralSetterName, Decl(classUsedBeforeInitializedVariables.ts, 28, 6)) >this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0)) ->withinObjectLiteralSetterName : Symbol(Test.withinObjectLiteralSetterName, Decl(classUsedBeforeInitializedVariables.ts, 22, 6)) ->_ : Symbol(_, Decl(classUsedBeforeInitializedVariables.ts, 25, 49)) +>withinObjectLiteralSetterName : Symbol(Test.withinObjectLiteralSetterName, Decl(classUsedBeforeInitializedVariables.ts, 28, 6)) +>_ : Symbol(_, Decl(classUsedBeforeInitializedVariables.ts, 31, 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)) +>withinClassDeclarationExtension : Symbol(Test.withinClassDeclarationExtension, Decl(classUsedBeforeInitializedVariables.ts, 32, 6)) +>this.withinClassDeclarationExtension : Symbol(Test.withinClassDeclarationExtension, Decl(classUsedBeforeInitializedVariables.ts, 32, 6)) >this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0)) ->withinClassDeclarationExtension : Symbol(Test.withinClassDeclarationExtension, Decl(classUsedBeforeInitializedVariables.ts, 26, 6)) +>withinClassDeclarationExtension : Symbol(Test.withinClassDeclarationExtension, Decl(classUsedBeforeInitializedVariables.ts, 32, 6)) + + fromOptional = this.p5; +>fromOptional : Symbol(Test.fromOptional, Decl(classUsedBeforeInitializedVariables.ts, 34, 100)) +>this.p5 : Symbol(Test.p5, Decl(classUsedBeforeInitializedVariables.ts, 4, 11)) +>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0)) +>p5 : Symbol(Test.p5, Decl(classUsedBeforeInitializedVariables.ts, 4, 11)) // 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)) +>assignedByArrowFunction : Symbol(Test.assignedByArrowFunction, Decl(classUsedBeforeInitializedVariables.ts, 36, 27)) +>this.assignedByFunction : Symbol(Test.assignedByFunction, Decl(classUsedBeforeInitializedVariables.ts, 40, 69)) >this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0)) ->assignedByFunction : Symbol(Test.assignedByFunction, Decl(classUsedBeforeInitializedVariables.ts, 32, 69)) +>assignedByFunction : Symbol(Test.assignedByFunction, Decl(classUsedBeforeInitializedVariables.ts, 40, 69)) assignedByFunction: any = (function () { ->assignedByFunction : Symbol(Test.assignedByFunction, Decl(classUsedBeforeInitializedVariables.ts, 32, 69)) +>assignedByFunction : Symbol(Test.assignedByFunction, Decl(classUsedBeforeInitializedVariables.ts, 40, 69)) return this.assignedByFunction; })(); diff --git a/tests/baselines/reference/classUsedBeforeInitializedVariables.types b/tests/baselines/reference/classUsedBeforeInitializedVariables.types index f10bf48a8ef..6a10797aa7e 100644 --- a/tests/baselines/reference/classUsedBeforeInitializedVariables.types +++ b/tests/baselines/reference/classUsedBeforeInitializedVariables.types @@ -22,6 +22,27 @@ class Test { >p4 : number >0 : 0 + p5?: number; +>p5 : number + + p6?: string; +>p6 : string + + p7 = { +>p7 : { hello: string; } +>{ hello: (this.p6 = "string"), } : { hello: string; } + + hello: (this.p6 = "string"), +>hello : string +>(this.p6 = "string") : "string" +>this.p6 = "string" : "string" +>this.p6 : string +>this : this +>p6 : string +>"string" : "string" + + }; + directlyAssigned: any = this.directlyAssigned; >directlyAssigned : any >this.directlyAssigned : any @@ -95,6 +116,12 @@ class Test { >this : this >withinClassDeclarationExtension : any + fromOptional = this.p5; +>fromOptional : number +>this.p5 : number +>this : this +>p5 : number + // These error cases are ignored (not checked by control flow analysis) assignedByArrowFunction: any = (() => this.assignedByFunction)(); diff --git a/tests/cases/compiler/classUsedBeforeInitializedVariables.ts b/tests/cases/compiler/classUsedBeforeInitializedVariables.ts index f10ae4e20a0..74acff583de 100644 --- a/tests/cases/compiler/classUsedBeforeInitializedVariables.ts +++ b/tests/cases/compiler/classUsedBeforeInitializedVariables.ts @@ -5,6 +5,12 @@ class Test { p2 = this.p1; p3 = this.p4; p4 = 0; + p5?: number; + + p6?: string; + p7 = { + hello: (this.p6 = "string"), + }; directlyAssigned: any = this.directlyAssigned; @@ -30,6 +36,8 @@ class Test { withinClassDeclarationExtension: any = (class extends this.withinClassDeclarationExtension { }); + fromOptional = this.p5; + // These error cases are ignored (not checked by control flow analysis) assignedByArrowFunction: any = (() => this.assignedByFunction)();