@@ -55,6 +55,10 @@ module ts {
55
55
return skipTrivia ( getSourceFileOfNode ( node ) . text , node . pos ) ;
56
56
}
57
57
58
+ export function getSourceTextOfNodeFromSourceText ( sourceText : string , node : Node ) : string {
59
+ return sourceText . substring ( skipTrivia ( sourceText , node . pos ) , node . end ) ;
60
+ }
61
+
58
62
export function getSourceTextOfNode ( node : Node ) : string {
59
63
var text = getSourceFileOfNode ( node ) . text ;
60
64
return text . substring ( skipTrivia ( text , node . pos ) , node . end ) ;
@@ -400,18 +404,98 @@ module ts {
400
404
401
405
var labelledStatementInfo = ( ( ) => {
402
406
// TODO(jfreeman): Implement a data structure for tracking labels
403
- var functionBoundarySentinel = < LabelledStatement > { } ;
404
- var currentLabelSet : Identifier [ ] ;
405
- var labelSetStack : Identifier [ ] [ ] ;
407
+ var functionBoundarySentinel : StringSet = { } ;
408
+ var currentLabelSet : StringSet ;
409
+ var labelSetStack : StringSet [ ] ;
406
410
var isIterationStack : boolean [ ] ;
407
411
412
+ function addLabel ( label : Identifier ) : void {
413
+ if ( ! currentLabelSet ) {
414
+ currentLabelSet = { } ;
415
+ }
416
+ currentLabelSet [ label . text ] = true ;
417
+ }
418
+
419
+ function pushCurrentLabelSet ( isIterationStatement : boolean ) : void {
420
+ if ( ! labelSetStack && ! isIterationStack ) {
421
+ labelSetStack = [ ] ;
422
+ isIterationStack = [ ] ;
423
+ }
424
+ Debug . assert ( currentLabelSet !== undefined ) ;
425
+ labelSetStack . push ( currentLabelSet ) ;
426
+ isIterationStack . push ( isIterationStatement ) ;
427
+ currentLabelSet = undefined ;
428
+ }
429
+
430
+ function pushFunctionBoundary ( ) : void {
431
+ if ( ! labelSetStack && ! isIterationStack ) {
432
+ labelSetStack = [ ] ;
433
+ isIterationStack = [ ] ;
434
+ }
435
+ Debug . assert ( currentLabelSet === undefined ) ;
436
+ labelSetStack . push ( functionBoundarySentinel ) ;
437
+
438
+ // It does not matter what we push here, since we will never ask if a function boundary
439
+ // is an iteration statement
440
+ isIterationStack . push ( false ) ;
441
+ }
442
+
443
+ function pop ( ) : void {
444
+ // Assert that we are in a "pushed" state
445
+ Debug . assert ( labelSetStack . length && isIterationStack . length && currentLabelSet === undefined ) ;
446
+ labelSetStack . pop ( ) ;
447
+ isIterationStack . pop ( ) ;
448
+ }
449
+
450
+ function nodeIsNestedInLabel ( label : Identifier , requireIterationStatement : boolean , stopAtFunctionBoundary : boolean ) : ControlBlockContext {
451
+ if ( ! requireIterationStatement && currentLabelSet && hasProperty ( currentLabelSet , label . text ) ) {
452
+ return ControlBlockContext . Nested ;
453
+ }
454
+
455
+ if ( ! labelSetStack ) {
456
+ return ControlBlockContext . NotNested ;
457
+ }
458
+
459
+ // We want to start searching for the label at the lowest point in the tree,
460
+ // and climb up from there. So we start at the end of the labelSetStack array.
461
+ var crossedFunctionBoundary = false ;
462
+ for ( var i = labelSetStack . length - 1 ; i >= 0 ; i -- ) {
463
+ var labelSet = labelSetStack [ i ] ;
464
+ // Not allowed to cross function boundaries, so stop if we encounter one
465
+ if ( labelSet === functionBoundarySentinel ) {
466
+ if ( stopAtFunctionBoundary ) {
467
+ break ;
468
+ }
469
+ else {
470
+ crossedFunctionBoundary = true ;
471
+ continue ;
472
+ }
473
+ }
474
+
475
+ // If we require an iteration statement, only search in the current
476
+ // statement if it is an iteration statement
477
+ if ( requireIterationStatement && isIterationStack [ i ] === false ) {
478
+ continue ;
479
+ }
480
+
481
+ if ( hasProperty ( labelSet , label . text ) ) {
482
+ return crossedFunctionBoundary ? ControlBlockContext . CrossingFunctionBoundary : ControlBlockContext . Nested ;
483
+ }
484
+ }
485
+
486
+ // This is a bit of a misnomer. If the caller passed true for stopAtFunctionBoundary,
487
+ // there actually may be an enclosing label across a function boundary, but we will
488
+ // just return NotNested
489
+ return ControlBlockContext . NotNested ;
490
+ }
491
+
408
492
// TODO(jfreeman): Fill in these stubs
409
493
return {
410
- addLabel : ( label : Identifier ) => { } ,
411
- pushCurrentLabelSet : ( isIterationStatement : boolean ) => { } ,
412
- pushFunctionBoundary : ( ) => { } ,
413
- pop : ( ) => { } ,
414
- labelExists : ( label : Identifier , requireIterationStatement : boolean ) : boolean => false ,
494
+ addLabel : addLabel ,
495
+ pushCurrentLabelSet : pushCurrentLabelSet ,
496
+ pushFunctionBoundary : pushFunctionBoundary ,
497
+ pop : pop ,
498
+ nodeIsNestedInLabel : nodeIsNestedInLabel ,
415
499
} ;
416
500
} ) ( ) ;
417
501
@@ -2083,40 +2167,39 @@ module ts {
2083
2167
parseExpected ( kind === SyntaxKind . BreakStatement ? SyntaxKind . BreakKeyword : SyntaxKind . ContinueKeyword ) ;
2084
2168
if ( ! isSemicolon ( ) ) node . label = parseIdentifier ( ) ;
2085
2169
parseSemicolon ( ) ;
2170
+ finishNode ( node ) ;
2086
2171
2087
2172
// In an ambient context, we will already give an error for having a statement.
2088
2173
if ( ! inAmbientContext && errorCountBeforeStatement === file . syntacticErrors . length ) {
2089
2174
if ( node . label ) {
2090
- checkBreakOrContinueStatementWithLabel ( node . label , kind ) ;
2175
+ checkBreakOrContinueStatementWithLabel ( node ) ;
2091
2176
}
2092
2177
else {
2093
- checkAnonymousBreakOrContinueStatement ( kind , keywordStart , keywordLength ) ;
2178
+ checkAnonymousBreakOrContinueStatement ( node ) ;
2094
2179
}
2095
2180
}
2096
- return finishNode ( node ) ;
2181
+ return node ;
2097
2182
}
2098
2183
2099
- function checkAnonymousBreakOrContinueStatement ( kind : SyntaxKind , errorStart : number , errorLength : number ) : void {
2100
- if ( kind === SyntaxKind . BreakStatement ) {
2184
+ function checkAnonymousBreakOrContinueStatement ( node : BreakOrContinueStatement ) : void {
2185
+ if ( node . kind === SyntaxKind . BreakStatement ) {
2101
2186
if ( inIterationStatement === ControlBlockContext . Nested
2102
2187
|| inSwitchStatement === ControlBlockContext . Nested ) {
2103
2188
return ;
2104
2189
}
2105
2190
else if ( inIterationStatement === ControlBlockContext . NotNested
2106
2191
&& inSwitchStatement === ControlBlockContext . NotNested ) {
2107
- grammarErrorAtPos ( errorStart , errorLength ,
2108
- Diagnostics . A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement ) ;
2192
+ grammarErrorOnNode ( node , Diagnostics . A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement ) ;
2109
2193
return ;
2110
2194
}
2111
2195
// Fall through
2112
2196
}
2113
- else if ( kind === SyntaxKind . ContinueStatement ) {
2197
+ else if ( node . kind === SyntaxKind . ContinueStatement ) {
2114
2198
if ( inIterationStatement === ControlBlockContext . Nested ) {
2115
2199
return ;
2116
2200
}
2117
2201
else if ( inIterationStatement === ControlBlockContext . NotNested ) {
2118
- grammarErrorAtPos ( errorStart , errorLength ,
2119
- Diagnostics . A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement ) ;
2202
+ grammarErrorOnNode ( node , Diagnostics . A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement ) ;
2120
2203
return ;
2121
2204
}
2122
2205
// Fall through
@@ -2127,19 +2210,31 @@ module ts {
2127
2210
2128
2211
Debug . assert ( inIterationStatement === ControlBlockContext . CrossingFunctionBoundary
2129
2212
|| inSwitchStatement === ControlBlockContext . CrossingFunctionBoundary ) ;
2130
- grammarErrorAtPos ( errorStart , errorLength , Diagnostics . Jump_target_cannot_cross_function_boundary ) ;
2213
+ grammarErrorOnNode ( node , Diagnostics . Jump_target_cannot_cross_function_boundary ) ;
2131
2214
}
2132
2215
2133
- function checkBreakOrContinueStatementWithLabel ( label : Identifier , kind : SyntaxKind ) : void {
2134
- if ( labelledStatementInfo . labelExists ( label , /*requireIterationStatement*/ kind === SyntaxKind . ContinueStatement ) ) {
2216
+ function checkBreakOrContinueStatementWithLabel ( node : BreakOrContinueStatement ) : void {
2217
+ // For error specificity, if the label is not found, we want to distinguish whether it is because
2218
+ // it crossed a function boundary or it was simply not found. To do this, we pass false for
2219
+ // stopAtFunctionBoundary.
2220
+ var nodeIsNestedInLabel = labelledStatementInfo . nodeIsNestedInLabel ( node . label ,
2221
+ /*requireIterationStatement*/ node . kind === SyntaxKind . ContinueStatement ,
2222
+ /*stopAtFunctionBoundary*/ false ) ;
2223
+ if ( nodeIsNestedInLabel === ControlBlockContext . Nested ) {
2224
+ return ;
2225
+ }
2226
+
2227
+ if ( nodeIsNestedInLabel === ControlBlockContext . CrossingFunctionBoundary ) {
2228
+ grammarErrorOnNode ( node , Diagnostics . Jump_target_cannot_cross_function_boundary ) ;
2135
2229
return ;
2136
2230
}
2137
2231
2138
- if ( kind === SyntaxKind . ContinueStatement ) {
2139
- grammarErrorOnNode ( label , Diagnostics . A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement ) ;
2232
+ // It is NotNested
2233
+ if ( node . kind === SyntaxKind . ContinueStatement ) {
2234
+ grammarErrorOnNode ( node , Diagnostics . A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement ) ;
2140
2235
}
2141
- else if ( kind === SyntaxKind . BreakStatement ) {
2142
- grammarErrorOnNode ( label , Diagnostics . A_break_statement_can_only_jump_to_a_label_of_an_enclosing_statement ) ;
2236
+ else if ( node . kind === SyntaxKind . BreakStatement ) {
2237
+ grammarErrorOnNode ( node , Diagnostics . A_break_statement_can_only_jump_to_a_label_of_an_enclosing_statement ) ;
2143
2238
}
2144
10000
2239
else {
2145
2240
Debug . fail ( "checkBreakOrContinueStatementWithLabel" ) ;
@@ -2304,8 +2399,8 @@ module ts {
2304
2399
node . label = parseIdentifier ( ) ;
2305
2400
parseExpected ( SyntaxKind . ColonToken ) ;
2306
2401
2307
- if ( labelledStatementInfo . labelExists ( node . label , /*requireIterationStatement*/ false ) ) {
2308
- grammarErrorOnNode ( node . label , Diagnostics . Duplicate_label_0 ) ;
2402
+ if ( labelledStatementInfo . nodeIsNestedInLabel ( node . label , /*requireIterationStatement*/ false , /*stopAtFunctionBoundary*/ true ) ) {
2403
+ grammarErrorOnNode ( node . label , Diagnostics . Duplicate_label_0 , getSourceTextOfNodeFromSourceText ( sourceText , node . label ) ) ;
2309
2404
}
2310
2405
labelledStatementInfo . addLabel ( node . label ) ;
2311
2406
0 commit comments