diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ad772bd1a70..3eead9fc957 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -383,20 +383,42 @@ namespace ts { // return undefined if we can't find a symbol. } - /** Returns true if node1 is defined before node 2**/ - function isDefinedBefore(node1: Node, node2: Node): boolean { - let file1 = getSourceFileOfNode(node1); - let file2 = getSourceFileOfNode(node2); + const enum RelativeLocation { + Unknown, + SameFileLocatedBefore, + SameFileLocatedAfter, + DifferentFilesLocatedBefore, + DifferentFilesLocatedAfter, + } + + function isLocatedBefore(origin: Node, target: Node): boolean { + switch (getRelativeLocation(origin, target)) { + // unknown is returned with nodes are in different files and order cannot be determined based on compilation settings + // optimistically assume this is ok + case RelativeLocation.Unknown: + case RelativeLocation.SameFileLocatedBefore: + case RelativeLocation.DifferentFilesLocatedBefore: + return true; + default: + return false; + } + } + + /** gets relative location of target comparing to origin **/ + function getRelativeLocation(origin: Node, target: Node): RelativeLocation { + let file1 = getSourceFileOfNode(origin); + let file2 = getSourceFileOfNode(target); if (file1 === file2) { - return node1.pos <= node2.pos; + return origin.pos > target.pos ? RelativeLocation.SameFileLocatedBefore : RelativeLocation.SameFileLocatedAfter; } if (!compilerOptions.outFile && !compilerOptions.out) { - return true; + // nodes are in different files and order cannot be determines + return RelativeLocation.Unknown; } let sourceFiles = host.getSourceFiles(); - return sourceFiles.indexOf(file1) <= sourceFiles.indexOf(file2); + return sourceFiles.indexOf(file1) > sourceFiles.indexOf(file2) ? RelativeLocation.DifferentFilesLocatedBefore : RelativeLocation.DifferentFilesLocatedAfter; } // Resolve a given name for a given meaning at a given location. An error is reported if the name was not found and @@ -628,31 +650,53 @@ namespace ts { Debug.assert(declaration !== undefined, "Block-scoped variable declaration is undefined"); // first check if usage is lexically located after the declaration - let isUsedBeforeDeclaration = !isDefinedBefore(declaration, errorLocation); - if (!isUsedBeforeDeclaration) { - // lexical check succeeded however code still can be illegal. - // - block scoped variables cannot be used in its initializers - // let x = x; // illegal but usage is lexically after definition - // - in ForIn/ForOf statements variable cannot be contained in expression part - // for (let x in x) - // for (let x of x) + let isUsedBeforeDeclaration = false; + switch (getRelativeLocation(declaration, errorLocation)) { + case RelativeLocation.DifferentFilesLocatedBefore: + isUsedBeforeDeclaration = true; + break; + case RelativeLocation.SameFileLocatedBefore: + // try to detect if forward reference to block scoped variable is inside function + // such forward references are permitted (they are still technically can be incorrect (i.e. in case of IIFEs) + // but detecting these case is more complicated task) + const declarationContainer = getEnclosingBlockScopeContainer(declaration); + let current = errorLocation; + while (current) { + if (current === declarationContainer) { + isUsedBeforeDeclaration = true; + break; + } + else if (isFunctionLike(current)) { + break; + } + current = current.parent; + } + break; + case RelativeLocation.SameFileLocatedAfter: + // lexical check succeeded however code still can be illegal. + // - block scoped variables cannot be used in its initializers + // let x = x; // illegal but usage is lexically after definition + // - in ForIn/ForOf statements variable cannot be contained in expression part + // for (let x in x) + // for (let x of x) - // climb up to the variable declaration skipping binding patterns - let variableDeclaration = getAncestor(declaration, SyntaxKind.VariableDeclaration); - let container = getEnclosingBlockScopeContainer(variableDeclaration); + // climb up to the variable declaration skipping binding patterns + let variableDeclaration = getAncestor(declaration, SyntaxKind.VariableDeclaration); + let container = getEnclosingBlockScopeContainer(variableDeclaration); - if (variableDeclaration.parent.parent.kind === SyntaxKind.VariableStatement || - variableDeclaration.parent.parent.kind === SyntaxKind.ForStatement) { - // variable statement/for statement case, - // use site should not be inside variable declaration (initializer of declaration or binding element) - isUsedBeforeDeclaration = isSameScopeDescendentOf(errorLocation, variableDeclaration, container); - } - else if (variableDeclaration.parent.parent.kind === SyntaxKind.ForOfStatement || - variableDeclaration.parent.parent.kind === SyntaxKind.ForInStatement) { - // ForIn/ForOf case - use site should not be used in expression part - let expression = (variableDeclaration.parent.parent).expression; - isUsedBeforeDeclaration = isSameScopeDescendentOf(errorLocation, expression, container); - } + if (variableDeclaration.parent.parent.kind === SyntaxKind.VariableStatement || + variableDeclaration.parent.parent.kind === SyntaxKind.ForStatement) { + // variable statement/for statement case, + // use site should not be inside variable declaration (initializer of declaration or binding element) + isUsedBeforeDeclaration = isSameScopeDescendentOf(errorLocation, variableDeclaration, container); + } + else if (variableDeclaration.parent.parent.kind === SyntaxKind.ForOfStatement || + variableDeclaration.parent.parent.kind === SyntaxKind.ForInStatement) { + // ForIn/ForOf case - use site should not be used in expression part + let expression = (variableDeclaration.parent.parent).expression; + isUsedBeforeDeclaration = isSameScopeDescendentOf(errorLocation, expression, container); + } + break; } if (isUsedBeforeDeclaration) { error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationNameToString(declaration.name)); @@ -13356,7 +13400,7 @@ namespace ts { } // illegal case: forward reference - if (!isDefinedBefore(propertyDecl, member)) { + if (isLocatedBefore(propertyDecl, member)) { reportError = false; error(e, Diagnostics.A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums); return undefined; diff --git a/tests/baselines/reference/blockScopedVariablesUseBeforeDef.js b/tests/baselines/reference/blockScopedVariablesUseBeforeDef.js new file mode 100644 index 00000000000..2792521344f --- /dev/null +++ b/tests/baselines/reference/blockScopedVariablesUseBeforeDef.js @@ -0,0 +1,74 @@ +//// [blockScopedVariablesUseBeforeDef.ts] +function foo1() { + let a = () => x; + let x; +} + +function foo2() { + let a = function () { return x; } + let x; +} + +function foo3() { + class X { + m() { return x;} + } + let x; +} + +function foo4() { + let y = class { + m() { return x; } + }; + let x; +} + +function foo5() { + let x = () => y; + let y = () => x; +} + +function foo6() { + function f() { + return x; + } + let x; +} + +//// [blockScopedVariablesUseBeforeDef.js] +function foo1() { + var a = function () { return x; }; + var x; +} +function foo2() { + var a = function () { return x; }; + var x; +} +function foo3() { + var X = (function () { + function X() { + } + X.prototype.m = function () { return x; }; + return X; + })(); + var x; +} +function foo4() { + var y = (function () { + function class_1() { + } + class_1.prototype.m = function () { return x; }; + return class_1; + })(); + var x; +} +function foo5() { + var x = function () { return y; }; + var y = function () { return x; }; +} +function foo6() { + function f() { + return x; + } + var x; +} diff --git a/tests/baselines/reference/blockScopedVariablesUseBeforeDef.symbols b/tests/baselines/reference/blockScopedVariablesUseBeforeDef.symbols new file mode 100644 index 00000000000..a528af236c1 --- /dev/null +++ b/tests/baselines/reference/blockScopedVariablesUseBeforeDef.symbols @@ -0,0 +1,76 @@ +=== tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts === +function foo1() { +>foo1 : Symbol(foo1, Decl(blockScopedVariablesUseBeforeDef.ts, 0, 0)) + + let a = () => x; +>a : Symbol(a, Decl(blockScopedVariablesUseBeforeDef.ts, 1, 4)) +>x : Symbol(x, Decl(blockScopedVariablesUseBeforeDef.ts, 2, 4)) + + let x; +>x : Symbol(x, Decl(blockScopedVariablesUseBeforeDef.ts, 2, 4)) +} + +function foo2() { +>foo2 : Symbol(foo2, Decl(blockScopedVariablesUseBeforeDef.ts, 3, 1)) + + let a = function () { return x; } +>a : Symbol(a, Decl(blockScopedVariablesUseBeforeDef.ts, 6, 4)) +>x : Symbol(x, Decl(blockScopedVariablesUseBeforeDef.ts, 7, 4)) + + let x; +>x : Symbol(x, Decl(blockScopedVariablesUseBeforeDef.ts, 7, 4)) +} + +function foo3() { +>foo3 : Symbol(foo3, Decl(blockScopedVariablesUseBeforeDef.ts, 8, 1)) + + class X { +>X : Symbol(X, Decl(blockScopedVariablesUseBeforeDef.ts, 10, 17)) + + m() { return x;} +>m : Symbol(m, Decl(blockScopedVariablesUseBeforeDef.ts, 11, 10)) +>x : Symbol(x, Decl(blockScopedVariablesUseBeforeDef.ts, 14, 4)) + } + let x; +>x : Symbol(x, Decl(blockScopedVariablesUseBeforeDef.ts, 14, 4)) +} + +function foo4() { +>foo4 : Symbol(foo4, Decl(blockScopedVariablesUseBeforeDef.ts, 15, 1)) + + let y = class { +>y : Symbol(y, Decl(blockScopedVariablesUseBeforeDef.ts, 18, 4)) + + m() { return x; } +>m : Symbol((Anonymous class).m, Decl(blockScopedVariablesUseBeforeDef.ts, 18, 16)) +>x : Symbol(x, Decl(blockScopedVariablesUseBeforeDef.ts, 21, 4)) + + }; + let x; +>x : Symbol(x, Decl(blockScopedVariablesUseBeforeDef.ts, 21, 4)) +} + +function foo5() { +>foo5 : Symbol(foo5, Decl(blockScopedVariablesUseBeforeDef.ts, 22, 1)) + + let x = () => y; +>x : Symbol(x, Decl(blockScopedVariablesUseBeforeDef.ts, 25, 4)) +>y : Symbol(y, Decl(blockScopedVariablesUseBeforeDef.ts, 26, 4)) + + let y = () => x; +>y : Symbol(y, Decl(blockScopedVariablesUseBeforeDef.ts, 26, 4)) +>x : Symbol(x, Decl(blockScopedVariablesUseBeforeDef.ts, 25, 4)) +} + +function foo6() { +>foo6 : Symbol(foo6, Decl(blockScopedVariablesUseBeforeDef.ts, 27, 1)) + + function f() { +>f : Symbol(f, Decl(blockScopedVariablesUseBeforeDef.ts, 29, 17)) + + return x; +>x : Symbol(x, Decl(blockScopedVariablesUseBeforeDef.ts, 33, 4)) + } + let x; +>x : Symbol(x, Decl(blockScopedVariablesUseBeforeDef.ts, 33, 4)) +} diff --git a/tests/baselines/reference/blockScopedVariablesUseBeforeDef.types b/tests/baselines/reference/blockScopedVariablesUseBeforeDef.types new file mode 100644 index 00000000000..bc3d5f8eba8 --- /dev/null +++ b/tests/baselines/reference/blockScopedVariablesUseBeforeDef.types @@ -0,0 +1,81 @@ +=== tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts === +function foo1() { +>foo1 : () => void + + let a = () => x; +>a : () => any +>() => x : () => any +>x : any + + let x; +>x : any +} + +function foo2() { +>foo2 : () => void + + let a = function () { return x; } +>a : () => any +>function () { return x; } : () => any +>x : any + + let x; +>x : any +} + +function foo3() { +>foo3 : () => void + + class X { +>X : X + + m() { return x;} +>m : () => any +>x : any + } + let x; +>x : any +} + +function foo4() { +>foo4 : () => void + + let y = class { +>y : typeof (Anonymous class) +>class { m() { return x; } } : typeof (Anonymous class) + + m() { return x; } +>m : () => any +>x : any + + }; + let x; +>x : any +} + +function foo5() { +>foo5 : () => void + + let x = () => y; +>x : () => () => any +>() => y : () => () => any +>y : () => () => any + + let y = () => x; +>y : () => () => any +>() => x : () => () => any +>x : () => () => any +} + +function foo6() { +>foo6 : () => void + + function f() { +>f : () => any + + return x; +>x : any + } + let x; +>x : any +} diff --git a/tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts b/tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts new file mode 100644 index 00000000000..7ded92df4c9 --- /dev/null +++ b/tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts @@ -0,0 +1,35 @@ +function foo1() { + let a = () => x; + let x; +} + +function foo2() { + let a = function () { return x; } + let x; +} + +function foo3() { + class X { + m() { return x;} + } + let x; +} + +function foo4() { + let y = class { + m() { return x; } + }; + let x; +} + +function foo5() { + let x = () => y; + let y = () => x; +} + +function foo6() { + function f() { + return x; + } + let x; +} \ No newline at end of file