mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-24 04:30:53 -06:00
allow forward references to block scoped variables from functions
This commit is contained in:
parent
c8ba16644b
commit
f952873ef1
@ -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 = <VariableDeclaration>getAncestor(declaration, SyntaxKind.VariableDeclaration);
|
||||
let container = getEnclosingBlockScopeContainer(variableDeclaration);
|
||||
// climb up to the variable declaration skipping binding patterns
|
||||
let variableDeclaration = <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 = (<ForInStatement | ForOfStatement>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 = (<ForInStatement | ForOfStatement>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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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))
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
35
tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts
Normal file
35
tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts
Normal file
@ -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;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user