@@ -336,11 +336,16 @@ module ts {
336
336
}
337
337
338
338
function fixupParentReferences ( sourceFile : SourceFile ) {
339
- // normally parent references are set during binding.
340
- // however here SourceFile data is used only for syntactic features so running the whole binding process is an overhead.
341
- // walk over the nodes and set parent references
339
+ // normally parent references are set during binding. However, for clients that only need
340
+ // a syntax tree, and no semantic features, then the binding process is an unnecessary
341
+ // overhead. This functions allows us to set all the parents, without all the expense of
342
+ // binding.
343
+
342
344
var parent : Node = sourceFile ;
343
- function walk ( n : Node ) : void {
345
+ forEachChild ( sourceFile , visitNode ) ;
346
+ return ;
347
+
348
+ function visitNode ( n : Node ) : void {
344
349
// walk down setting parents that differ from the parent we think it should be. This
345
350
// allows us to quickly bail out of setting parents for subtrees during incremental
346
351
// parsing
@@ -349,30 +354,49 @@ module ts {
349
354
350
355
var saveParent = parent ;
351
356
parent = n ;
352
- forEachChild ( n , walk ) ;
357
+ forEachChild ( n , visitNode ) ;
353
358
parent = saveParent ;
354
359
}
355
360
}
361
+ }
362
+
363
+ function shouldCheckNode ( node : Node ) {
364
+ switch ( node . kind ) {
365
+ case SyntaxKind . StringLiteral :
366
+ case SyntaxKind . NumericLiteral :
367
+ case SyntaxKind . Identifier :
368
+ return true ;
369
+ }
356
370
357
- forEachChild ( sourceFile , walk ) ;
371
+ return false ;
358
372
}
359
373
360
- function moveElementEntirelyPastChangeRange ( element : IncrementalElement , delta : number ) {
374
+ function moveElementEntirelyPastChangeRange ( element : IncrementalElement , delta : number , oldText : string , newText : string , aggressiveChecks : boolean ) {
361
375
if ( element . length ) {
362
376
visitArray ( < IncrementalNodeArray > element ) ;
363
377
}
364
378
else {
365
379
visitNode ( < IncrementalNode > element ) ;
366
380
}
381
+ return ;
367
382
368
383
function visitNode ( node : IncrementalNode ) {
384
+ if ( aggressiveChecks && shouldCheckNode ( node ) ) {
385
+ var text = oldText . substring ( node . pos , node . end ) ;
386
+ }
387
+
369
388
// Ditch any existing LS children we may have created. This way we can avoid
370
389
// moving them forward.
371
390
node . _children = undefined ;
372
391
node . pos += delta ;
373
392
node . end += delta ;
374
393
394
+ if ( aggressiveChecks && shouldCheckNode ( node ) ) {
395
+ Debug . assert ( text === newText . substring ( node . pos , node . end ) ) ;
396
+ }
397
+
375
398
forEachChild ( node , visitNode , visitArray ) ;
399
+ checkNodePositions ( node , aggressiveChecks ) ;
376
400
}
377
401
378
402
function visitArray ( array : IncrementalNodeArray ) {
@@ -459,14 +483,35 @@ module ts {
459
483
}
460
484
}
461
485
462
- function updateTokenPositionsAndMarkElements ( node : IncrementalNode , changeStart : number , changeRangeOldEnd : number , changeRangeNewEnd : number , delta : number ) : void {
463
- visitNode ( node ) ;
486
+ function checkNodePositions ( node : Node , aggressiveChecks : boolean ) {
487
+ if ( aggressiveChecks ) {
488
+ var pos = node . pos ;
489
+ forEachChild ( node , child => {
490
+ Debug . assert ( child . pos >= pos ) ;
491
+ pos = child . end ;
492
+ } ) ;
493
+ Debug . assert ( pos <= node . end ) ;
494
+ }
495
+ }
496
+
497
+ function updateTokenPositionsAndMarkElements (
498
+ sourceFile : IncrementalNode ,
499
+ changeStart : number ,
500
+ changeRangeOldEnd : number ,
501
+ changeRangeNewEnd : number ,
502
+ delta : number ,
503
+ oldText : string ,
504
+ newText : string ,
505
+ aggressiveChecks : boolean ) : void {
506
+
507
+ visitNode ( sourceFile ) ;
508
+ return ;
464
509
465
510
function visitNode ( child : IncrementalNode ) {
466
511
if ( child . pos > changeRangeOldEnd ) {
467
512
// Node is entirely past the change range. We need to move both its pos and
468
513
// end, forward or backward appropriately.
469
- moveElementEntirelyPastChangeRange ( child , delta ) ;
514
+ moveElementEntirelyPastChangeRange ( child , delta , oldText , newText , aggressiveChecks ) ;
470
515
return ;
471
516
}
472
517
@@ -480,6 +525,8 @@ module ts {
480
525
// Adjust the pos or end (or both) of the intersecting element accordingly.
481
526
adjustIntersectingElement ( child , changeStart , changeRangeOldEnd , changeRangeNewEnd , delta ) ;
482
527
forEachChild ( child , visitNode , visitArray ) ;
528
+
529
+ checkNodePositions ( child , aggressiveChecks ) ;
483
530
return ;
484
531
}
485
532
@@ -490,7 +537,7 @@ module ts {
490
537
if ( array . pos > changeRangeOldEnd ) {
491
538
// Array is entirely after the change range. We need to move it, and move any of
492
539
// its children.
493
- moveElementEntirelyPastChangeRange ( array , delta ) ;
540
+ moveElementEntirelyPastChangeRange ( array , delta , oldText , newText , aggressiveChecks ) ;
494
541
}
495
542
else {
496
543
// Check if the element intersects the change range. If it does, then it is not
@@ -513,7 +560,6 @@ module ts {
513
560
}
514
561
}
515
562
516
-
517
563
function extendToAffectedRange ( sourceFile : SourceFile , changeRange : TextChangeRange ) : TextChangeRange {
518
564
// Consider the following code:
519
565
// void foo() { /; }
@@ -534,6 +580,7 @@ module ts {
534
580
// start of the tree.
535
581
for ( var i = 0 ; start > 0 && i <= maxLookahead ; i ++ ) {
536
582
var nearestNode = findNearestNodeStartingBeforeOrAtPosition ( sourceFile , start ) ;
583
+ Debug . assert ( nearestNode . pos <= start ) ;
537
584
var position = nearestNode . pos ;
538
585
539
586
start = Math . max ( 0 , position - 1 ) ;
@@ -640,6 +687,22 @@ module ts {
640
687
}
641
688
}
642
689
690
+ function checkChangeRange ( sourceFile : SourceFile , newText : string , textChangeRange : TextChangeRange , aggressiveChecks : boolean ) {
691
+ var oldText = sourceFile . text ;
692
+ if ( textChangeRange ) {
693
+ Debug . assert ( ( oldText . length - textChangeRange . span . length + textChangeRange . newLength ) === newText . length ) ;
694
+
695
+ if ( aggressiveChecks || Debug . shouldAssert ( AssertionLevel . VeryAggressive ) ) {
696
+ var oldTextPrefix = oldText . substr ( 0 , textChangeRange . span . start ) ;
697
+ var newTextPrefix = newText . substr ( 0 , textChangeRange . span . start ) ;
698
+ Debug . assert ( oldTextPrefix === newTextPrefix ) ;
699
+
700
+ var oldTextSuffix = oldText . substring ( textSpanEnd ( textChangeRange . span ) , oldText . length ) ;
701
+ var newTextSuffix = newText . substring ( textSpanEnd ( textChangeRangeNewSpan ( textChangeRange ) ) , newText . length ) ;
702
+ Debug . assert ( oldTextSuffix === newTextSuffix ) ;
703
+ }
704
+ }
705
+ }
643
706
644
707
// Produces a new SourceFile for the 'newText' provided. The 'textChangeRange' parameter
645
708
// indicates what changed between the 'text' that this SourceFile has and the 'newText'.
@@ -650,7 +713,10 @@ module ts {
650
713
// from this SourceFile that are being held onto may change as a result (including
651
714
// becoming detached from any SourceFile). It is recommended that this SourceFile not
652
715
// be used once 'update' is called on it.
653
- export function updateSourceFile ( sourceFile : SourceFile , newText : string , textChangeRange : TextChangeRange ) : SourceFile {
716
+ export function updateSourceFile ( sourceFile : SourceFile , newText : string , textChangeRange : TextChangeRange , aggressiveChecks ?: boolean ) : SourceFile {
717
+ aggressiveChecks = aggressiveChecks || Debug . shouldAssert ( AssertionLevel . Aggressive ) ;
718
+
719
+ checkChangeRange ( sourceFile , newText , textChangeRange , aggressiveChecks ) ;
654
720
if ( textChangeRangeIsUnchanged ( textChangeRange ) ) {
655
721
// if the text didn't change, then we can just return our current source file as-is.
656
722
return sourceFile ;
@@ -659,23 +725,31 @@ module ts {
659
725
if ( sourceFile . statements . length === 0 ) {
660
726
// If we don't have any statements in the current source file, then there's no real
661
727
// way to incrementally parse. So just do a full parse instead.
662
- return parseSourceFile ( sourceFile . fileName , newText , sourceFile . languageVersion , /*syntaxCursor*/ undefined , /*setNodeParents*/ true )
728
+ return parseSourceFile ( sourceFile . fileName , newText , sourceFile . languageVersion , /*syntaxCursor*/ undefined , /*setNodeParents*/ true )
663
729
}
664
730
731
+ var oldText = sourceFile . text ;
665
732
var syntaxCursor = createSyntaxCursor ( sourceFile ) ;
666
733
667
734
// Make the actual change larger so that we know to reparse anything whose lookahead
668
735
// might have intersected the change.
669
736
var changeRange = extendToAffectedRange ( sourceFile , textChangeRange ) ;
737
+ checkChangeRange ( sourceFile , newText , changeRange , aggressiveChecks ) ;
738
+
739
+ // Ensure that extending the affected range only moved the start of the change range
740
+ // earlier in the file.
741
+ Debug . assert ( changeRange . span . start <= textChangeRange . span . start ) ;
742
+ Debug . assert ( textSpanEnd ( changeRange . span ) === textSpanEnd ( textChangeRange . span ) ) ;
743
+ Debug . assert ( textSpanEnd ( textChangeRangeNewSpan ( changeRange ) ) === textSpanEnd ( textChangeRangeNewSpan ( textChangeRange ) ) ) ;
670
744
671
745
// The is the amount the nodes after the edit range need to be adjusted. It can be
672
746
// positive (if the edit added characters), negative (if the edit deleted characters)
673
747
// or zero (if this was a pure overwrite with nothing added/removed).
674
748
var delta = textChangeRangeNewSpan ( changeRange ) . length - changeRange . span . length ;
675
749
676
750
// If we added or removed characters during the edit, then we need to go and adjust all
677
- // the nodes after the edit. Those nodes may move forward down (if we inserted chars)
678
- // or they may move backward (if we deleted chars).
751
+ // the nodes after the edit. Those nodes may move forward (if we inserted chars) or they
752
+ // may move backward (if we deleted chars).
679
753
//
680
754
// Doing this helps us out in two ways. First, it means that any nodes/tokens we want
681
755
// to reuse are already at the appropriate position in the new text. That way when we
@@ -693,7 +767,7 @@ module ts {
693
767
// Also, mark any syntax elements that intersect the changed span. We know, up front,
694
768
// that we cannot reuse these elements.
695
769
updateTokenPositionsAndMarkElements ( < IncrementalNode > < Node > sourceFile ,
696
- changeRange . span . start , textSpanEnd ( changeRange . span ) , textSpanEnd ( textChangeRangeNewSpan ( changeRange ) ) , delta ) ;
770
+ changeRange . span . start , textSpanEnd ( changeRange . span ) , textSpanEnd ( textChangeRangeNewSpan ( changeRange ) ) , delta , oldText , newText , aggressiveChecks ) ;
697
771
698
772
// Now that we've set up our internal incremental state just proceed and parse the
699
773
// source file in the normal fashion. When possible the parser will retrieve and
@@ -863,7 +937,6 @@ module ts {
863
937
var identifiers : Map < string > = { } ;
864
938
var identifierCount = 0 ;
865
939
var nodeCount = 0 ;
866
- var scanner : Scanner ;
867
940
var token : SyntaxKind ;
868
941
869
942
var sourceFile = < SourceFile > createNode ( SyntaxKind . SourceFile , /*pos*/ 0 ) ;
@@ -956,7 +1029,7 @@ module ts {
956
1029
var parseErrorBeforeNextFinishedNode : boolean = false ;
957
1030
958
1031
// Create and prime the scanner before parsing the source elements.
959
- scanner = createScanner ( languageVersion , /*skipTrivia*/ true , sourceText , scanError ) ;
1032
+ var scanner = createScanner ( languageVersion , /*skipTrivia*/ true , sourceText , scanError ) ;
960
1033
token = nextToken ( ) ;
961
1034
962
1035
processReferenceComments ( sourceFile ) ;
@@ -975,6 +1048,7 @@ module ts {
975
1048
fixupParentReferences ( sourceFile ) ;
976
1049
}
977
1050
1051
+ syntaxCursor = undefined ;
978
1052
return sourceFile ;
979
1053
980
1054
function setContextFlag ( val : Boolean , flag : ParserContextFlags ) {
0 commit comments