@@ -13,6 +13,8 @@ public class SyntaxEditor
1313 {
1414 private readonly SyntaxGenerator _generator ;
1515 private readonly List < Change > _changes ;
16+ private bool _allowEditsOnLazilyCreatedTrackedNewNodes ;
17+ private HashSet < SyntaxNode > _lazyTrackedNewNodesOpt ;
1618
1719 /// <summary>
1820 /// Creates a new <see cref="SyntaxEditor"/> instance.
@@ -36,6 +38,30 @@ internal SyntaxEditor(SyntaxNode root, SyntaxGenerator generator)
3638 _changes = new List < Change > ( ) ;
3739 }
3840
41+ private SyntaxNode ApplyTrackingToNewNode ( SyntaxNode node )
42+ {
43+ if ( node == null )
44+ {
45+ return null ;
46+ }
47+
48+ _lazyTrackedNewNodesOpt = _lazyTrackedNewNodesOpt ?? new HashSet < SyntaxNode > ( ) ;
49+ foreach ( var descendant in node . DescendantNodesAndSelf ( ) )
50+ {
51+ _lazyTrackedNewNodesOpt . Add ( descendant ) ;
52+ }
53+
54+ return node . TrackNodes ( node . DescendantNodesAndSelf ( ) ) ;
55+ }
56+
57+ private IEnumerable < SyntaxNode > ApplyTrackingToNewNodes ( IEnumerable < SyntaxNode > nodes )
58+ {
59+ foreach ( var node in nodes )
60+ {
61+ yield return ApplyTrackingToNewNode ( node ) ;
62+ }
63+ }
64+
3965 /// <summary>
4066 /// The <see cref="SyntaxNode"/> that was specified when the <see cref="SyntaxEditor"/> was constructed.
4167 /// </summary>
@@ -51,7 +77,8 @@ internal SyntaxEditor(SyntaxNode root, SyntaxGenerator generator)
5177 /// </summary>
5278 public SyntaxNode GetChangedRoot ( )
5379 {
54- var nodes = Enumerable . Distinct ( _changes . Select ( c => c . Node ) ) ;
80+ var nodes = Enumerable . Distinct ( _changes . Where ( c => OriginalRoot . Contains ( c . Node ) )
81+ . Select ( c => c . Node ) ) ;
5582 var newRoot = OriginalRoot . TrackNodes ( nodes ) ;
5683
5784 foreach ( var change in _changes )
@@ -67,7 +94,7 @@ public SyntaxNode GetChangedRoot()
6794 /// </summary>
6895 public void TrackNode ( SyntaxNode node )
6996 {
70- CheckNodeInTree ( node ) ;
97+ CheckNodeInOriginalTreeOrTracked ( node ) ;
7198 _changes . Add ( new NoChange ( node ) ) ;
7299 }
73100
@@ -87,7 +114,7 @@ public void RemoveNode(SyntaxNode node)
87114 /// <param name="options">Options that affect how node removal works.</param>
88115 public void RemoveNode ( SyntaxNode node , SyntaxRemoveOptions options )
89116 {
90- CheckNodeInTree ( node ) ;
117+ CheckNodeInOriginalTreeOrTracked ( node ) ;
91118 _changes . Add ( new RemoveChange ( node , options ) ) ;
92119 }
93120
@@ -99,14 +126,26 @@ public void RemoveNode(SyntaxNode node, SyntaxRemoveOptions options)
99126 /// The node passed into the compute function includes changes from prior edits. It will not appear as a descendant of the original root.</param>
100127 public void ReplaceNode ( SyntaxNode node , Func < SyntaxNode , SyntaxGenerator , SyntaxNode > computeReplacement )
101128 {
102- CheckNodeInTree ( node ) ;
103- _changes . Add ( new ReplaceChange ( node , computeReplacement ) ) ;
129+ CheckNodeInOriginalTreeOrTracked ( node ) ;
130+ if ( computeReplacement == null )
131+ {
132+ throw new ArgumentNullException ( nameof ( computeReplacement ) ) ;
133+ }
134+
135+ _allowEditsOnLazilyCreatedTrackedNewNodes = true ;
136+ _changes . Add ( new ReplaceChange ( node , computeReplacement , this ) ) ;
104137 }
105138
106139 internal void ReplaceNode < TArgument > ( SyntaxNode node , Func < SyntaxNode , SyntaxGenerator , TArgument , SyntaxNode > computeReplacement , TArgument argument )
107140 {
108- CheckNodeInTree ( node ) ;
109- _changes . Add ( new ReplaceChange < TArgument > ( node , computeReplacement , argument ) ) ;
141+ CheckNodeInOriginalTreeOrTracked ( node ) ;
142+ if ( computeReplacement == null )
143+ {
144+ throw new ArgumentNullException ( nameof ( computeReplacement ) ) ;
145+ }
146+
147+ _allowEditsOnLazilyCreatedTrackedNewNodes = true ;
148+ _changes . Add ( new ReplaceChange < TArgument > ( node , computeReplacement , argument , this ) ) ;
110149 }
111150
112151 /// <summary>
@@ -116,13 +155,14 @@ internal void ReplaceNode<TArgument>(SyntaxNode node, Func<SyntaxNode, SyntaxGen
116155 /// <param name="newNode">The new node that will be placed into the tree in the existing node's location.</param>
117156 public void ReplaceNode ( SyntaxNode node , SyntaxNode newNode )
118157 {
119- CheckNodeInTree ( node ) ;
158+ CheckNodeInOriginalTreeOrTracked ( node ) ;
120159 if ( node == newNode )
121160 {
122161 return ;
123162 }
124163
125- this . ReplaceNode ( node , ( n , g ) => newNode ) ;
164+ newNode = ApplyTrackingToNewNode ( newNode ) ;
165+ _changes . Add ( new ReplaceChange ( node , ( n , g ) => newNode , this ) ) ;
126166 }
127167
128168 /// <summary>
@@ -132,7 +172,13 @@ public void ReplaceNode(SyntaxNode node, SyntaxNode newNode)
132172 /// <param name="newNodes">The nodes to place before the existing node. These nodes must be of a compatible type to be placed in the same list containing the existing node.</param>
133173 public void InsertBefore ( SyntaxNode node , IEnumerable < SyntaxNode > newNodes )
134174 {
135- CheckNodeInTree ( node ) ;
175+ CheckNodeInOriginalTreeOrTracked ( node ) ;
176+ if ( newNodes == null )
177+ {
178+ throw new ArgumentNullException ( nameof ( newNodes ) ) ;
179+ }
180+
181+ newNodes = ApplyTrackingToNewNodes ( newNodes ) ;
136182 _changes . Add ( new InsertChange ( node , newNodes , isBefore : true ) ) ;
137183 }
138184
@@ -142,10 +188,7 @@ public void InsertBefore(SyntaxNode node, IEnumerable<SyntaxNode> newNodes)
142188 /// <param name="node">The node already existing in the tree that the new nodes will be placed before. This must be a node this is contained within a syntax list.</param>
143189 /// <param name="newNode">The node to place before the existing node. This node must be of a compatible type to be placed in the same list containing the existing node.</param>
144190 public void InsertBefore ( SyntaxNode node , SyntaxNode newNode )
145- {
146- CheckNodeInTree ( node ) ;
147- this . InsertBefore ( node , new [ ] { newNode } ) ;
148- }
191+ => InsertBefore ( node , new [ ] { newNode } ) ;
149192
150193 /// <summary>
151194 /// Insert the new nodes after the specified node already existing in the tree.
@@ -154,7 +197,13 @@ public void InsertBefore(SyntaxNode node, SyntaxNode newNode)
154197 /// <param name="newNodes">The nodes to place after the existing node. These nodes must be of a compatible type to be placed in the same list containing the existing node.</param>
155198 public void InsertAfter ( SyntaxNode node , IEnumerable < SyntaxNode > newNodes )
156199 {
157- CheckNodeInTree ( node ) ;
200+ CheckNodeInOriginalTreeOrTracked ( node ) ;
201+ if ( newNodes == null )
202+ {
203+ throw new ArgumentNullException ( nameof ( newNodes ) ) ;
204+ }
205+
206+ newNodes = ApplyTrackingToNewNodes ( newNodes ) ;
158207 _changes . Add ( new InsertChange ( node , newNodes , isBefore : false ) ) ;
159208 }
160209
@@ -164,17 +213,37 @@ public void InsertAfter(SyntaxNode node, IEnumerable<SyntaxNode> newNodes)
164213 /// <param name="node">The node already existing in the tree that the new nodes will be placed after. This must be a node this is contained within a syntax list.</param>
165214 /// <param name="newNode">The node to place after the existing node. This node must be of a compatible type to be placed in the same list containing the existing node.</param>
166215 public void InsertAfter ( SyntaxNode node , SyntaxNode newNode )
167- {
168- CheckNodeInTree ( node ) ;
169- this . InsertAfter ( node , new [ ] { newNode } ) ;
170- }
216+ => this . InsertAfter ( node , new [ ] { newNode } ) ;
171217
172- private void CheckNodeInTree ( SyntaxNode node )
218+ private void CheckNodeInOriginalTreeOrTracked ( SyntaxNode node )
173219 {
174- if ( ! OriginalRoot . Contains ( node ) )
220+ if ( node == null )
221+ {
222+ throw new ArgumentNullException ( nameof ( node ) ) ;
223+ }
224+
225+ if ( OriginalRoot . Contains ( node ) )
175226 {
176- throw new ArgumentException ( Microsoft . CodeAnalysis . WorkspacesResources . The_node_is_not_part_of_the_tree , nameof ( node ) ) ;
227+ // Node is contained in the original tree.
228+ return ;
229+ }
230+
231+ if ( _allowEditsOnLazilyCreatedTrackedNewNodes )
232+ {
233+ // This could be a node that is handed to us lazily from one of the prior edits
234+ // which support lazy replacement nodes, we conservatively avoid throwing here.
235+ // If this was indeed an unsupported node, syntax editor will throw an exception later
236+ // when attempting to compute changed root.
237+ return ;
177238 }
239+
240+ if ( _lazyTrackedNewNodesOpt ? . Contains ( node ) == true )
241+ {
242+ // Node is one of the new nodes, which is already tracked and supported.
243+ return ;
244+ }
245+
246+ throw new ArgumentException ( WorkspacesResources . The_node_is_not_part_of_the_tree , nameof ( node ) ) ;
178247 }
179248
180249 private abstract class Change
@@ -221,17 +290,23 @@ public override SyntaxNode Apply(SyntaxNode root, SyntaxGenerator generator)
221290 private class ReplaceChange : Change
222291 {
223292 private readonly Func < SyntaxNode , SyntaxGenerator , SyntaxNode > _modifier ;
293+ private readonly SyntaxEditor _editor ;
224294
225- public ReplaceChange ( SyntaxNode node , Func < SyntaxNode , SyntaxGenerator , SyntaxNode > modifier )
295+ public ReplaceChange (
296+ SyntaxNode node ,
297+ Func < SyntaxNode , SyntaxGenerator , SyntaxNode > modifier ,
298+ SyntaxEditor editor )
226299 : base ( node )
227300 {
228301 _modifier = modifier ;
302+ _editor = editor ;
229303 }
230304
231305 public override SyntaxNode Apply ( SyntaxNode root , SyntaxGenerator generator )
232306 {
233307 var current = root . GetCurrentNode ( this . Node ) ;
234308 var newNode = _modifier ( current , generator ) ;
309+ newNode = _editor . ApplyTrackingToNewNode ( newNode ) ;
235310 return generator . ReplaceNode ( root , current , newNode ) ;
236311 }
237312 }
@@ -240,21 +315,25 @@ private class ReplaceChange<TArgument> : Change
240315 {
241316 private readonly Func < SyntaxNode , SyntaxGenerator , TArgument , SyntaxNode > _modifier ;
242317 private readonly TArgument _argument ;
318+ private readonly SyntaxEditor _editor ;
243319
244320 public ReplaceChange (
245321 SyntaxNode node ,
246322 Func < SyntaxNode , SyntaxGenerator , TArgument , SyntaxNode > modifier ,
247- TArgument argument )
323+ TArgument argument ,
324+ SyntaxEditor editor )
248325 : base ( node )
249326 {
250327 _modifier = modifier ;
251328 _argument = argument ;
329+ _editor = editor ;
252330 }
253331
254332 public override SyntaxNode Apply ( SyntaxNode root , SyntaxGenerator generator )
255333 {
256334 var current = root . GetCurrentNode ( this . Node ) ;
257335 var newNode = _modifier ( current , generator , _argument ) ;
336+ newNode = _editor . ApplyTrackingToNewNode ( newNode ) ;
258337 return generator . ReplaceNode ( root , current , newNode ) ;
259338 }
260339 }
0 commit comments