Skip to content

Commit c28edc3

Browse files
committed
Error on forward references for property initializers
The error only appears when a property initializer references another property before its definition. References to outer variables, etc are still allowed.
1 parent 501084a commit c28edc3

File tree

1 file changed

+29
-12
lines changed

1 file changed

+29
-12
lines changed

src/compiler/checker.ts

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,7 @@ namespace ts {
641641
}
642642
// declaration is after usage
643643
// can be legal if usage is deferred (i.e. inside function or in initializer of instance property)
644-
if (isUsedInFunctionOrNonStaticProperty(usage)) {
644+
if (isUsedInFunctionOrInstanceProperty(usage)) {
645645
return true;
646646
}
647647
const sourceFiles = host.getSourceFiles();
@@ -668,10 +668,12 @@ namespace ts {
668668
}
669669

670670

671-
// declaration is after usage
672-
// can be legal if usage is deferred (i.e. inside function or in initializer of instance property)
671+
// declaration is after usage, but it can still be legal if usage is deferred:
672+
// 1. inside a function
673+
// 2. inside an instance property initializer, a reference to a non-instance property
673674
const container = getEnclosingBlockScopeContainer(declaration);
674-
return isUsedInFunctionOrNonStaticProperty(usage, container);
675+
const isInstanceProperty = declaration.kind === SyntaxKind.PropertyDeclaration && !(getModifierFlags(declaration) & ModifierFlags.Static);
676+
return isUsedInFunctionOrInstanceProperty(usage, isInstanceProperty, container);
675677

676678
function isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration: VariableDeclaration, usage: Node): boolean {
677679
const container = getEnclosingBlockScopeContainer(declaration);
@@ -700,7 +702,7 @@ namespace ts {
700702
return false;
701703
}
702704

703-
function isUsedInFunctionOrNonStaticProperty(usage: Node, container?: Node): boolean {
705+
function isUsedInFunctionOrInstanceProperty(usage: Node, isDeclarationInstanceProperty?: boolean, container?: Node): boolean {
704706
let current = usage;
705707
while (current) {
706708
if (current === container) {
@@ -711,13 +713,13 @@ namespace ts {
711713
return true;
712714
}
713715

714-
const initializerOfNonStaticProperty = current.parent &&
716+
const initializerOfInstanceProperty = current.parent &&
715717
current.parent.kind === SyntaxKind.PropertyDeclaration &&
716718
(getModifierFlags(current.parent) & ModifierFlags.Static) === 0 &&
717719
(<PropertyDeclaration>current.parent).initializer === current;
718720

719-
if (initializerOfNonStaticProperty) {
720-
return true;
721+
if (initializerOfInstanceProperty) {
722+
return !isDeclarationInstanceProperty;
721723
}
722724

723725
current = current.parent;
@@ -986,10 +988,10 @@ namespace ts {
986988
// interface bar {}
987989
// }
988990
// const foo/*1*/: foo/*2*/.bar;
989-
// The foo at /*1*/ and /*2*/ will share same symbol with two meaning
990-
// block - scope variable and namespace module. However, only when we
991+
// The foo at /*1*/ and /*2*/ will share same symbol with two meanings:
992+
// block-scoped variable and namespace module. However, only when we
991993
// try to resolve name in /*1*/ which is used in variable position,
992-
// we want to check for block- scoped
994+
// we want to check for block-scoped
993995
if (meaning & SymbolFlags.BlockScopedVariable) {
994996
const exportOrLocalSymbol = getExportSymbolOfValueSymbolIfExported(result);
995997
if (exportOrLocalSymbol.flags & SymbolFlags.BlockScopedVariable) {
@@ -1013,7 +1015,7 @@ namespace ts {
10131015
return false;
10141016
}
10151017

1016-
const container = getThisContainer(errorLocation, /* includeArrowFunctions */ true);
1018+
const container = getThisContainer(errorLocation, /*includeArrowFunctions*/ true);
10171019
let location = container;
10181020
while (location) {
10191021
if (isClassLike(location.parent)) {
@@ -12543,6 +12545,16 @@ namespace ts {
1254312545
}
1254412546
}
1254512547

12548+
function isInPropertyInitializer(node: Node): boolean {
12549+
while (node) {
12550+
if (node.parent && node.parent.kind === SyntaxKind.PropertyDeclaration && (node.parent as PropertyDeclaration).initializer === node) {
12551+
return true;
12552+
}
12553+
node = node.parent;
12554+
}
12555+
return false;
12556+
}
12557+
1254612558
function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) {
1254712559
const type = checkNonNullExpression(left);
1254812560
if (isTypeAny(type) || type === silentNeverType) {
@@ -12565,6 +12577,11 @@ namespace ts {
1256512577
}
1256612578
return unknownType;
1256712579
}
12580+
if (prop.valueDeclaration &&
12581+
isInPropertyInitializer(node) &&
12582+
!isBlockScopedNameDeclaredBeforeUse(prop.valueDeclaration, right)) {
12583+
error(right, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, right.text);
12584+
}
1256812585

1256912586
markPropertyAsReferenced(prop);
1257012587

0 commit comments

Comments
 (0)