1+ /// <reference path="..\..\..\src\harness\external\mocha.d.ts" />
2+ /// <reference path="..\..\..\src\compiler\parser.ts" />
3+
4+ module ts {
5+ function withChange ( text : IScriptSnapshot , start : number , length : number , newText : string ) : { text : IScriptSnapshot ; textChangeRange : TextChangeRange ; } {
6+ var contents = text . getText ( 0 , text . getLength ( ) ) ;
7+ var newContents = contents . substr ( 0 , start ) + newText + contents . substring ( start + length ) ;
8+
9+ return { text : ScriptSnapshot . fromString ( newContents ) , textChangeRange : new TextChangeRange ( new TextSpan ( start , length ) , newText . length ) }
10+ }
11+
12+ function withInsert ( text : IScriptSnapshot , start : number , newText : string ) : { text : IScriptSnapshot ; textChangeRange : TextChangeRange ; } {
13+ return withChange ( text , start , 0 , newText ) ;
14+ }
15+
16+ function withDelete ( text : IScriptSnapshot , start : number , length : number ) : { text : IScriptSnapshot ; textChangeRange : TextChangeRange ; } {
17+ return withChange ( text , start , length , "" ) ;
18+ }
19+
20+ // NOTE: 'reusedElements' is the expected count of elements reused from the old tree to the new
21+ // tree. It may change as we tweak the parser. If the count increases then that should always
22+ // be a good thing. If it decreases, that's not great (less reusability), but that may be
23+ // unavoidable. If it does decrease an investigation should be done to make sure that things
24+ // are still ok and we're still appropriately reusing most of the tree.
25+ function compareTrees ( oldText : IScriptSnapshot , newText : IScriptSnapshot , textChangeRange : TextChangeRange , expectedReusedElements : number = - 1 ) : void {
26+ // Create a tree for the new text, in a non-incremental fashion.
27+ var options : CompilerOptions = { } ;
28+ options . target = ScriptTarget . ES5 ;
29+
30+ var newTree = createLanguageServiceSourceFile ( /*fileName:*/ "" , newText , options , /*version:*/ "0" , /*isOpen:*/ true ) ;
31+ Utils . checkInvariants ( newTree , /*parent:*/ undefined ) ;
32+
33+ // Create a tree for the new text, in an incremental fashion.
34+ var oldTree = createLanguageServiceSourceFile ( /*fileName:*/ "" , oldText , options , /*version:*/ "0" , /*isOpen:*/ true ) ;
35+ Utils . checkInvariants ( oldTree , /*parent:*/ undefined ) ;
36+
37+ var incrementalNewTree = oldTree . update ( newText , "1" , /*isOpen:*/ true , textChangeRange ) ;
38+ Utils . checkInvariants ( incrementalNewTree , /*parent:*/ undefined ) ;
39+
40+ // We should get the same tree when doign a full or incremental parse.
41+ assertStructuralEquals ( newTree , incrementalNewTree ) ;
42+
43+ // There should be no reused nodes between two trees that are fully parsed.
44+ Debug . assert ( reusedElements ( oldTree , newTree ) === 0 ) ;
45+
46+ if ( expectedReusedElements !== - 1 ) {
47+ var actualReusedCount = reusedElements ( oldTree , incrementalNewTree ) ;
48+ Debug . assert ( actualReusedCount === expectedReusedElements , actualReusedCount + " !== " + expectedReusedElements ) ;
49+ }
50+ }
51+
52+ function assertStructuralEquals ( node1 : Node , node2 : Node ) {
53+ if ( node1 === node2 ) {
54+ return ;
55+ }
56+
57+ if ( ! node1 || ! node2 ) {
58+ throw new Error ( "!node1 || !node2" ) ;
59+ }
60+
61+ if ( node1 . pos !== node2 . pos ) {
62+ throw new Error ( "node1.pos !== node2.pos" ) ;
63+ }
64+
65+ if ( node1 . end !== node2 . end ) {
66+ throw new Error ( "node1.end !== node2.end" ) ;
67+ }
68+
69+ if ( node1 . kind !== node2 . kind ) {
70+ throw new Error ( "node1.kind !== node2.kind" ) ;
71+ }
72+
73+ if ( node1 . flags !== node2 . flags ) {
74+ throw new Error ( "node1.flags !== node2.flags" ) ;
75+ }
76+
77+ if ( node1 . parserContextFlags !== node2 . parserContextFlags ) {
78+ throw new Error ( "node1.parserContextFlags !== node2.parserContextFlags" ) ;
79+ }
80+
81+ forEachChild ( node1 ,
82+ child1 => {
83+ var childName = findChildName ( node1 , child1 ) ;
84+ var child2 : Node = ( < any > node2 ) [ childName ] ;
85+
86+ assertStructuralEquals ( child1 , child2 ) ;
87+ } ,
88+ ( array1 : NodeArray < Node > ) => {
89+ var childName = findChildName ( node1 , array1 ) ;
90+ var array2 : NodeArray < Node > = ( < any > node2 ) [ childName ] ;
91+
92+ assertArrayStructuralEquals ( array1 , array2 ) ;
93+ } ) ;
94+ }
95+
96+ function assertArrayStructuralEquals ( array1 : NodeArray < Node > , array2 : NodeArray < Node > ) {
97+ if ( array1 === array2 ) {
98+ return ;
99+ }
100+
101+ if ( ! array1 || ! array2 ) {
102+ throw new Error ( "!array1 || !array2" ) ;
103+ }
104+
105+ if ( array1 . pos !== array2 . pos ) {
106+ throw new Error ( "array1.pos !== array2.pos" ) ;
107+ }
108+
109+ if ( array1 . end !== array2 . end ) {
110+ throw new Error ( "array1.end !== array2.end" ) ;
111+ }
112+
113+ if ( array1 . length !== array2 . length ) {
114+ throw new Error ( "array1.length !== array2.length" ) ;
115+ }
116+
117+ for ( var i = 0 , n = array1 . length ; i < n ; i ++ ) {
118+ assertStructuralEquals ( array1 [ i ] , array2 [ i ] ) ;
119+ }
120+ }
121+
122+ function findChildName ( parent : any , child : any ) {
123+ for ( var name in parent ) {
124+ if ( parent . hasOwnProperty ( name ) && parent [ name ] === child ) {
125+ return name ;
126+ }
127+ }
128+
129+ throw new Error ( "Could not find child in parent" ) ;
130+ }
131+
132+ function reusedElements ( oldNode : SourceFile , newNode : SourceFile ) : number {
133+ var allOldElements = collectElements ( oldNode ) ;
134+ var allNewElements = collectElements ( newNode ) ;
135+
136+ return filter ( allOldElements , v => contains ( allNewElements , v ) ) . length ;
137+ }
138+
139+ function collectElements ( node : Node ) {
140+ var result : Node [ ] = [ ] ;
141+ visit ( node ) ;
142+ return result ;
143+
144+ function visit ( node : Node ) {
145+ result . push ( node ) ;
146+ forEachChild ( node , visit ) ;
147+ }
148+ }
149+
150+ describe ( 'Incremental' , ( ) => {
151+ it ( 'Inserting into method' , ( ) => {
152+ var source = "class C {\r\n" +
153+ " public foo1() { }\r\n" +
154+ " public foo2() {\r\n" +
155+ " return 1;\r\n" +
156+ " }\r\n" +
157+ " public foo3() { }\r\n" +
158+ "}" ;
159+
160+ var oldText = ScriptSnapshot . fromString ( source ) ;
161+ var semicolonIndex = source . indexOf ( ";" ) ;
162+ var newTextAndChange = withInsert ( oldText , semicolonIndex , " + 1" ) ;
163+ compareTrees ( oldText , newTextAndChange . text , newTextAndChange . textChangeRange , 0 ) ;
164+ } ) ;
165+ } ) ;
166+ }
0 commit comments