@@ -7183,7 +7183,7 @@ namespace ts {
7183
7183
7184
7184
checkCollisionWithCapturedSuperVariable(node, node);
7185
7185
checkCollisionWithCapturedThisVariable(node, node);
7186
- checkBlockScopedBindingCapturedInLoop (node, symbol);
7186
+ checkNestedBlockScopedBinding (node, symbol);
7187
7187
7188
7188
return getNarrowedTypeOfSymbol(getExportSymbolOfValueSymbolIfExported(symbol), node);
7189
7189
}
@@ -7200,7 +7200,7 @@ namespace ts {
7200
7200
return false;
7201
7201
}
7202
7202
7203
- function checkBlockScopedBindingCapturedInLoop (node: Identifier, symbol: Symbol): void {
7203
+ function checkNestedBlockScopedBinding (node: Identifier, symbol: Symbol): void {
7204
7204
if (languageVersion >= ScriptTarget.ES6 ||
7205
7205
(symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.Class)) === 0 ||
7206
7206
symbol.valueDeclaration.parent.kind === SyntaxKind.CatchClause) {
@@ -7212,40 +7212,31 @@ namespace ts {
7212
7212
// 2. walk from the declaration up to the boundary of lexical environment and check
7213
7213
// if there is an iteration statement in between declaration and boundary (is binding/class declared inside iteration statement)
7214
7214
7215
- let container: Node;
7216
- if (symbol.flags & SymbolFlags.Class) {
7217
- // get parent of class declaration
7218
- container = getClassLikeDeclarationOfSymbol(symbol).parent;
7219
- }
7220
- else {
7221
- // nesting structure:
7222
- // (variable declaration or binding element) -> variable declaration list -> container
7223
- container = symbol.valueDeclaration;
7224
- while (container.kind !== SyntaxKind.VariableDeclarationList) {
7225
- container = container.parent;
7226
- }
7227
- // get the parent of variable declaration list
7228
- container = container.parent;
7229
- if (container.kind === SyntaxKind.VariableStatement) {
7230
- // if parent is variable statement - get its parent
7231
- container = container.parent;
7232
- }
7233
- }
7234
-
7235
- const inFunction = isInsideFunction(node.parent, container);
7236
-
7215
+ const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration);
7216
+ const usedInFunction = isInsideFunction(node.parent, container);
7237
7217
let current = container;
7218
+
7219
+ let containedInIterationStatement = false;
7238
7220
while (current && !nodeStartsNewLexicalEnvironment(current)) {
7239
7221
if (isIterationStatement(current, /*lookInLabeledStatements*/ false)) {
7240
- if (inFunction) {
7241
- getNodeLinks(current).flags |= NodeCheckFlags.LoopWithBlockScopedBindingCapturedInFunction;
7242
- }
7243
- // mark value declaration so during emit they can have a special handling
7244
- getNodeLinks(<VariableDeclaration>symbol.valueDeclaration).flags |= NodeCheckFlags.BlockScopedBindingInLoop;
7222
+ containedInIterationStatement = true;
7245
7223
break;
7246
7224
}
7247
7225
current = current.parent;
7248
7226
}
7227
+
7228
+ if (containedInIterationStatement) {
7229
+ if (usedInFunction) {
7230
+ // mark iteration statement as containing block-scoped binding captured in some function
7231
+ getNodeLinks(current).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding;
7232
+ }
7233
+ // set 'declared inside loop' bit on the block-scoped binding
7234
+ getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.BlockScopedBindingInLoop;
7235
+ }
7236
+
7237
+ if (usedInFunction) {
7238
+ getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.CapturedBlockScopedBinding;
7239
+ }
7249
7240
}
7250
7241
7251
7242
function captureLexicalThis(node: Node, container: Node): void {
@@ -15652,42 +15643,61 @@ namespace ts {
15652
15643
return symbol && symbol.flags & SymbolFlags.Alias ? getDeclarationOfAliasSymbol(symbol) : undefined;
15653
15644
}
15654
15645
15655
- function isStatementWithLocals(node: Node) {
15656
- switch (node.kind) {
15657
- case SyntaxKind.Block:
15658
- case SyntaxKind.CaseBlock:
15659
- case SyntaxKind.ForStatement:
15660
- case SyntaxKind.ForInStatement:
15661
- case SyntaxKind.ForOfStatement:
15662
- return true;
15663
- }
15664
- return false;
15665
- }
15666
-
15667
- function isNestedRedeclarationSymbol(symbol: Symbol): boolean {
15646
+ function isSymbolOfDeclarationWithCollidingName(symbol: Symbol): boolean {
15668
15647
if (symbol.flags & SymbolFlags.BlockScoped) {
15669
15648
const links = getSymbolLinks(symbol);
15670
- if (links.isNestedRedeclaration === undefined) {
15649
+ if (links.isDeclaratonWithCollidingName === undefined) {
15671
15650
const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration);
15672
- links.isNestedRedeclaration = isStatementWithLocals(container) &&
15673
- !!resolveName(container.parent, symbol.name, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined);
15651
+ if (isStatementWithLocals(container)) {
15652
+ const nodeLinks = getNodeLinks(symbol.valueDeclaration);
15653
+ if (!!resolveName(container.parent, symbol.name, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined)) {
15654
+ // redeclaration - always should be renamed
15655
+ links.isDeclaratonWithCollidingName = true;
15656
+ }
15657
+ else if (nodeLinks.flags & NodeCheckFlags.CapturedBlockScopedBinding) {
15658
+ // binding is captured in the function
15659
+ // should be renamed if:
15660
+ // - binding is not top level - top level bindings never collide with anything
15661
+ // AND
15662
+ // - binding is not declared in loop, should be renamed to avoid name reuse across siblings
15663
+ // let a, b
15664
+ // { let x = 1; a = () => x; }
15665
+ // { let x = 100; b = () => x; }
15666
+ // console.log(a()); // should print '1'
15667
+ // console.log(b()); // should print '100'
15668
+ // OR
15669
+ // - binding is declared inside loop but not in inside initializer of iteration statement or directly inside loop body
15670
+ // * variables from initializer are passed to rewritted loop body as parameters so they are not captured directly
15671
+ // * variables that are declared immediately in loop body will become top level variable after loop is rewritten and thus
15672
+ // they will not collide with anything
15673
+ const isDeclaredInLoop = nodeLinks.flags & NodeCheckFlags.BlockScopedBindingInLoop;
15674
+ const inLoopInitializer = isIterationStatement(container, /*lookInLabeledStatements*/ false);
15675
+ const inLoopBodyBlock = container.kind === SyntaxKind.Block && isIterationStatement(container.parent, /*lookInLabeledStatements*/ false);
15676
+
15677
+ links.isDeclaratonWithCollidingName = !isBlockScopedContainerTopLevel(container) && (!isDeclaredInLoop || (!inLoopInitializer && !inLoopBodyBlock));
15678
+ }
15679
+ else {
15680
+ links.isDeclaratonWithCollidingName = false;
15681
+ }
15682
+ }
15674
15683
}
15675
- return links.isNestedRedeclaration ;
15684
+ return links.isDeclaratonWithCollidingName ;
15676
15685
}
15677
15686
return false;
15678
15687
}
15679
15688
15680
15689
// When resolved as an expression identifier, if the given node references a nested block scoped entity with
15681
- // a name that hides an existing name, return the declaration of that entity. Otherwise, return undefined.
15682
- function getReferencedNestedRedeclaration(node: Identifier): Declaration {
15690
+ // a name that either hides an existing name or might hide it when compiled downlevel,
15691
+ // return the declaration of that entity. Otherwise, return undefined.
15692
+ function getReferencedDeclarationWithCollidingName(node: Identifier): Declaration {
15683
15693
const symbol = getReferencedValueSymbol(node);
15684
- return symbol && isNestedRedeclarationSymbol (symbol) ? symbol.valueDeclaration : undefined;
15694
+ return symbol && isSymbolOfDeclarationWithCollidingName (symbol) ? symbol.valueDeclaration : undefined;
15685
15695
}
15686
15696
15687
- // Return true if the given node is a declaration of a nested block scoped entity with a name that hides an
15688
- // existing name.
15689
- function isNestedRedeclaration (node: Declaration): boolean {
15690
- return isNestedRedeclarationSymbol (getSymbolOfNode(node));
15697
+ // Return true if the given node is a declaration of a nested block scoped entity with a name that either hides an
15698
+ // existing name or might hide a name when compiled downlevel
15699
+ function isDeclarationWithCollidingName (node: Declaration): boolean {
15700
+ return isSymbolOfDeclarationWithCollidingName (getSymbolOfNode(node));
15691
15701
}
15692
15702
15693
15703
function isValueAliasDeclaration(node: Node): boolean {
@@ -15888,8 +15898,8 @@ namespace ts {
15888
15898
return {
15889
15899
getReferencedExportContainer,
15890
15900
getReferencedImportDeclaration,
15891
- getReferencedNestedRedeclaration ,
15892
- isNestedRedeclaration ,
15901
+ getReferencedDeclarationWithCollidingName ,
15902
+ isDeclarationWithCollidingName ,
15893
15903
isValueAliasDeclaration,
15894
15904
hasGlobalName,
15895
15905
isReferencedAliasDeclaration,
0 commit comments