Skip to content

Commit 5b12593

Browse files
authored
Merge pull request #31558 from mavasani/SyntaxEditorTracking
Add support to Replace and Insert SyntaxEditor APIs to allow tracking…
2 parents 10ffcf7 + 180ef2c commit 5b12593

File tree

2 files changed

+395
-27
lines changed

2 files changed

+395
-27
lines changed

src/Workspaces/Core/Portable/Editing/SyntaxEditor.cs

Lines changed: 103 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)