diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b5306771dd7..4423b574e31 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -641,7 +641,7 @@ namespace ts { } // declaration is after usage // can be legal if usage is deferred (i.e. inside function or in initializer of instance property) - if (isUsedInFunctionOrNonStaticProperty(usage)) { + if (isUsedInFunctionOrInstanceProperty(usage)) { return true; } const sourceFiles = host.getSourceFiles(); @@ -668,10 +668,12 @@ namespace ts { } - // declaration is after usage - // can be legal if usage is deferred (i.e. inside function or in initializer of instance property) + // declaration is after usage, but it can still be legal if usage is deferred: + // 1. inside a function + // 2. inside an instance property initializer, a reference to a non-instance property const container = getEnclosingBlockScopeContainer(declaration); - return isUsedInFunctionOrNonStaticProperty(usage, container); + const isInstanceProperty = declaration.kind === SyntaxKind.PropertyDeclaration && !(getModifierFlags(declaration) & ModifierFlags.Static); + return isUsedInFunctionOrInstanceProperty(usage, isInstanceProperty, container); function isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration: VariableDeclaration, usage: Node): boolean { const container = getEnclosingBlockScopeContainer(declaration); @@ -700,7 +702,7 @@ namespace ts { return false; } - function isUsedInFunctionOrNonStaticProperty(usage: Node, container?: Node): boolean { + function isUsedInFunctionOrInstanceProperty(usage: Node, isDeclarationInstanceProperty?: boolean, container?: Node): boolean { let current = usage; while (current) { if (current === container) { @@ -711,13 +713,13 @@ namespace ts { return true; } - const initializerOfNonStaticProperty = current.parent && + const initializerOfInstanceProperty = current.parent && current.parent.kind === SyntaxKind.PropertyDeclaration && (getModifierFlags(current.parent) & ModifierFlags.Static) === 0 && (current.parent).initializer === current; - if (initializerOfNonStaticProperty) { - return true; + if (initializerOfInstanceProperty) { + return !isDeclarationInstanceProperty; } current = current.parent; @@ -986,10 +988,10 @@ namespace ts { // interface bar {} // } // const foo/*1*/: foo/*2*/.bar; - // The foo at /*1*/ and /*2*/ will share same symbol with two meaning - // block - scope variable and namespace module. However, only when we + // The foo at /*1*/ and /*2*/ will share same symbol with two meanings: + // block-scoped variable and namespace module. However, only when we // try to resolve name in /*1*/ which is used in variable position, - // we want to check for block- scoped + // we want to check for block-scoped if (meaning & SymbolFlags.BlockScopedVariable) { const exportOrLocalSymbol = getExportSymbolOfValueSymbolIfExported(result); if (exportOrLocalSymbol.flags & SymbolFlags.BlockScopedVariable) { @@ -1013,7 +1015,7 @@ namespace ts { return false; } - const container = getThisContainer(errorLocation, /* includeArrowFunctions */ true); + const container = getThisContainer(errorLocation, /*includeArrowFunctions*/ true); let location = container; while (location) { if (isClassLike(location.parent)) { @@ -12543,6 +12545,16 @@ namespace ts { } } + function isInPropertyInitializer(node: Node): boolean { + while (node) { + if (node.parent && node.parent.kind === SyntaxKind.PropertyDeclaration && (node.parent as PropertyDeclaration).initializer === node) { + return true; + } + node = node.parent; + } + return false; + } + function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) { const type = checkNonNullExpression(left); if (isTypeAny(type) || type === silentNeverType) { @@ -12565,6 +12577,11 @@ namespace ts { } return unknownType; } + if (prop.valueDeclaration && + isInPropertyInitializer(node) && + !isBlockScopedNameDeclaredBeforeUse(prop.valueDeclaration, right)) { + error(right, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, right.text); + } markPropertyAsReferenced(prop); diff --git a/tests/baselines/reference/forwardRefInClassProperties.errors.txt b/tests/baselines/reference/forwardRefInClassProperties.errors.txt new file mode 100644 index 00000000000..dcf153076ed --- /dev/null +++ b/tests/baselines/reference/forwardRefInClassProperties.errors.txt @@ -0,0 +1,27 @@ +tests/cases/compiler/forwardRefInClassProperties.ts(3,15): error TS2448: Block-scoped variable '_a' used before its declaration. +tests/cases/compiler/forwardRefInClassProperties.ts(6,22): error TS2448: Block-scoped variable '_A' used before its declaration. +tests/cases/compiler/forwardRefInClassProperties.ts(11,17): error TS2448: Block-scoped variable 'b' used before its declaration. + + +==== tests/cases/compiler/forwardRefInClassProperties.ts (3 errors) ==== + class Test + { + _b = this._a; // undefined, no error/warning + ~~ +!!! error TS2448: Block-scoped variable '_a' used before its declaration. + _a = 3; + + static _B = Test._A; // undefined, no error/warning + ~~ +!!! error TS2448: Block-scoped variable '_A' used before its declaration. + static _A = 3; + + method() + { + let a = b; // Block-scoped variable 'b' used before its declaration + ~ +!!! error TS2448: Block-scoped variable 'b' used before its declaration. + let b = 3; + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/forwardRefInClassProperties.js b/tests/baselines/reference/forwardRefInClassProperties.js new file mode 100644 index 00000000000..1dc455729ab --- /dev/null +++ b/tests/baselines/reference/forwardRefInClassProperties.js @@ -0,0 +1,31 @@ +//// [forwardRefInClassProperties.ts] +class Test +{ + _b = this._a; // undefined, no error/warning + _a = 3; + + static _B = Test._A; // undefined, no error/warning + static _A = 3; + + method() + { + let a = b; // Block-scoped variable 'b' used before its declaration + let b = 3; + } +} + + +//// [forwardRefInClassProperties.js] +var Test = (function () { + function Test() { + this._b = this._a; // undefined, no error/warning + this._a = 3; + } + Test.prototype.method = function () { + var a = b; // Block-scoped variable 'b' used before its declaration + var b = 3; + }; + return Test; +}()); +Test._B = Test._A; // undefined, no error/warning +Test._A = 3; diff --git a/tests/cases/compiler/forwardRefInClassProperties.ts b/tests/cases/compiler/forwardRefInClassProperties.ts new file mode 100644 index 00000000000..80819b1a175 --- /dev/null +++ b/tests/cases/compiler/forwardRefInClassProperties.ts @@ -0,0 +1,14 @@ +class Test +{ + _b = this._a; // undefined, no error/warning + _a = 3; + + static _B = Test._A; // undefined, no error/warning + static _A = 3; + + method() + { + let a = b; // Block-scoped variable 'b' used before its declaration + let b = 3; + } +}