Skip to content

Commit 5f2588f

Browse files
committed
show error if block scoped variable declared in the loop is captured in closure
1 parent 7f5fb8b commit 5f2588f

File tree

4 files changed

+51
-0
lines changed

4 files changed

+51
-0
lines changed

src/compiler/checker.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4905,10 +4905,55 @@ module ts {
49054905

49064906
checkCollisionWithCapturedSuperVariable(node, node);
49074907
checkCollisionWithCapturedThisVariable(node, node);
4908+
checkBlockScopedBindingCapturedInLoop(node, symbol);
49084909

49094910
return getNarrowedTypeOfSymbol(getExportSymbolOfValueSymbolIfExported(symbol), node);
49104911
}
49114912

4913+
function isNameScopeBoundary(n: Node): boolean {
4914+
return isAnyFunction(n) || n.kind === SyntaxKind.ModuleDeclaration || n.kind === SyntaxKind.SourceFile;
4915+
}
4916+
4917+
function checkBlockScopedBindingCapturedInLoop(node: Identifier, symbol: Symbol): void {
4918+
if (languageVersion >= ScriptTarget.ES6 || (symbol.flags & SymbolFlags.BlockScopedVariable) === 0) {
4919+
return;
4920+
}
4921+
4922+
// - check if binding is used in some function
4923+
// (stop the walk when reaching container of binding declaration)
4924+
// - if first check succeeded - check if variable is declared inside the loop
4925+
4926+
// var decl -> var decl list -> parent
4927+
var container = (<VariableDeclaration>symbol.valueDeclaration).parent.parent;
4928+
if (container.kind === SyntaxKind.VariableStatement) {
4929+
container = container.parent;
4930+
}
4931+
4932+
var inFunction = false;
4933+
var current = node.parent;
4934+
while (current && current !== container) {
4935+
if (isAnyFunction(current)) {
4936+
inFunction = true;
4937+
break;
4938+
}
4939+
current = current.parent;
4940+
}
4941+
4942+
if (!inFunction) {
4943+
return;
4944+
}
4945+
4946+
var current: Node = container;
4947+
while (current && !isNameScopeBoundary(current)) {
4948+
if (isIterationStatement(current, /*lookInLabeledStatements*/ false)) {
4949+
getNodeLinks(current).flags |= NodeCheckFlags.BlockScopedBindingCapturedInLoop;
4950+
grammarErrorOnFirstToken(current, Diagnostics.Code_in_the_loop_captures_block_scoped_variable_0_in_closure_This_is_natively_supported_in_ECMAScript_6_or_higher, declarationNameToString(node));
4951+
break;
4952+
}
4953+
current = current.parent;
4954+
}
4955+
}
4956+
49124957
function captureLexicalThis(node: Node, container: Node): void {
49134958
var classNode = container.parent && container.parent.kind === SyntaxKind.ClassDeclaration ? container.parent : undefined;
49144959
getNodeLinks(node).flags |= NodeCheckFlags.LexicalThis;

src/compiler/diagnosticInformationMap.generated.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,7 @@ module ts {
382382
Property_0_does_not_exist_on_const_enum_1: { code: 4088, category: DiagnosticCategory.Error, key: "Property '{0}' does not exist on 'const' enum '{1}'." },
383383
let_is_not_allowed_to_be_used_as_a_name_in_let_or_const_declarations: { code: 4089, category: DiagnosticCategory.Error, key: "'let' is not allowed to be used as a name in 'let' or 'const' declarations." },
384384
Cannot_initialize_outer_scoped_variable_0_in_the_same_scope_as_block_scoped_declaration_1: { code: 4090, category: DiagnosticCategory.Error, key: "Cannot initialize outer scoped variable '{0}' in the same scope as block scoped declaration '{1}'." },
385+
Code_in_the_loop_captures_block_scoped_variable_0_in_closure_This_is_natively_supported_in_ECMAScript_6_or_higher: { code: 4091, category: DiagnosticCategory.Error, key: "Code in the loop captures block-scoped variable '{0}' in closure. This is natively supported in ECMAScript 6 or higher." },
385386
The_current_host_does_not_support_the_0_option: { code: 5001, category: DiagnosticCategory.Error, key: "The current host does not support the '{0}' option." },
386387
Cannot_find_the_common_subdirectory_path_for_the_input_files: { code: 5009, category: DiagnosticCategory.Error, key: "Cannot find the common subdirectory path for the input files." },
387388
Cannot_read_file_0_Colon_1: { code: 5012, category: DiagnosticCategory.Error, key: "Cannot read file '{0}': {1}" },

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,6 +1521,10 @@
15211521
"category": "Error",
15221522
"code": 4090
15231523
},
1524+
"Code in the loop captures block-scoped variable '{0}' in closure. This is natively supported in ECMAScript 6 or higher.": {
1525+
"category": "Error",
1526+
"code": 4091
1527+
},
15241528
"The current host does not support the '{0}' option.": {
15251529
"category": "Error",
15261530
"code": 5001

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1262,6 +1262,7 @@ module ts {
12621262

12631263
// Values for enum members have been computed, and any errors have been reported for them.
12641264
EnumValuesComputed = 0x00000080,
1265+
BlockScopedBindingCapturedInLoop = 0x00000100,
12651266
}
12661267

12671268
export interface NodeLinks {

0 commit comments

Comments
 (0)