/src/Workspaces/Core/Portable/Editing/SyntaxEditor.cs
C# | 266 lines | 172 code | 35 blank | 59 comment | 8 complexity | bcfe50658a9c0672aee7594abdcad2cc MD5 | raw file
1// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2
3using System;
4using System.Collections.Generic;
5using System.Linq;
6using System.Text;
7using System.Threading.Tasks;
8using Roslyn.Utilities;
9
10namespace Microsoft.CodeAnalysis.Editing
11{
12 /// <summary>
13 /// An editor for making changes to a syntax tree.
14 /// </summary>
15 public class SyntaxEditor
16 {
17 private readonly SyntaxGenerator _generator;
18 private readonly SyntaxNode _root;
19 private readonly List<Change> _changes;
20
21 /// <summary>
22 /// Creates a new <see cref="SyntaxEditor"/> instance.
23 /// </summary>
24 public SyntaxEditor(SyntaxNode root, Workspace workspace)
25 {
26 if (root == null)
27 {
28 throw new ArgumentNullException(nameof(root));
29 }
30
31 if (workspace == null)
32 {
33 throw new ArgumentNullException(nameof(workspace));
34 }
35
36 _root = root;
37 _generator = SyntaxGenerator.GetGenerator(workspace, root.Language);
38 _changes = new List<Change>();
39 }
40
41 /// <summary>
42 /// The <see cref="SyntaxNode"/> that was specified when the <see cref="SyntaxEditor"/> was constructed.
43 /// </summary>
44 public SyntaxNode OriginalRoot
45 {
46 get { return _root; }
47 }
48
49 /// <summary>
50 /// A <see cref="SyntaxGenerator"/> to use to create and change <see cref="SyntaxNode"/>'s.
51 /// </summary>
52 public SyntaxGenerator Generator
53 {
54 get { return _generator; }
55 }
56
57 /// <summary>
58 /// Returns the changed root node.
59 /// </summary>
60 public SyntaxNode GetChangedRoot()
61 {
62 var nodes = Enumerable.Distinct(_changes.Select(c => c.Node));
63 var newRoot = _root.TrackNodes(nodes);
64
65 foreach (var change in _changes)
66 {
67 newRoot = change.Apply(newRoot, _generator);
68 }
69
70 return newRoot;
71 }
72
73 /// <summary>
74 /// Makes sure the node is tracked, even if it is not changed.
75 /// </summary>
76 public void TrackNode(SyntaxNode node)
77 {
78 CheckNodeInTree(node);
79 _changes.Add(new NoChange(node));
80 }
81
82 /// <summary>
83 /// Remove the node from the tree.
84 /// </summary>
85 /// <param name="node">The node to remove that currently exists as part of the tree.</param>
86 public void RemoveNode(SyntaxNode node)
87 {
88 RemoveNode(node, SyntaxGenerator.DefaultRemoveOptions);
89 }
90
91 /// <summary>
92 /// Remove the node from the tree.
93 /// </summary>
94 /// <param name="node">The node to remove that currently exists as part of the tree.</param>
95 /// <param name="options">Options that affect how node removal works.</param>
96 public void RemoveNode(SyntaxNode node, SyntaxRemoveOptions options)
97 {
98 CheckNodeInTree(node);
99 _changes.Add(new RemoveChange(node, options));
100 }
101
102 /// <summary>
103 /// Replace the specified node with a node produced by the function.
104 /// </summary>
105 /// <param name="node">The node to replace that already exists in the tree.</param>
106 /// <param name="computeReplacement">A function that computes a replacement node.
107 /// The node passed into the compute function includes changes from prior edits. It will not appear as a descendant of the original root.</param>
108 public void ReplaceNode(SyntaxNode node, Func<SyntaxNode, SyntaxGenerator, SyntaxNode> computeReplacement)
109 {
110 CheckNodeInTree(node);
111 _changes.Add(new ReplaceChange(node, computeReplacement));
112 }
113
114 /// <summary>
115 /// Replace the specified node with a different node.
116 /// </summary>
117 /// <param name="node">The node to replace that already exists in the tree.</param>
118 /// <param name="newNode">The new node that will be placed into the tree in the existing node's location.</param>
119 public void ReplaceNode(SyntaxNode node, SyntaxNode newNode)
120 {
121 CheckNodeInTree(node);
122 if (node == newNode)
123 {
124 return;
125 }
126
127 this.ReplaceNode(node, (n, g) => newNode);
128 }
129
130 /// <summary>
131 /// Insert the new nodes before the specified node already existing in the tree.
132 /// </summary>
133 /// <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>
134 /// <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>
135 public void InsertBefore(SyntaxNode node, IEnumerable<SyntaxNode> newNodes)
136 {
137 CheckNodeInTree(node);
138 _changes.Add(new InsertChange(node, newNodes, isBefore: true));
139 }
140
141 /// <summary>
142 /// Insert the new node before the specified node already existing in the tree.
143 /// </summary>
144 /// <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>
145 /// <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>
146 public void InsertBefore(SyntaxNode node, SyntaxNode newNode)
147 {
148 CheckNodeInTree(node);
149 this.InsertBefore(node, new[] { newNode });
150 }
151
152 /// <summary>
153 /// Insert the new nodes after the specified node already existing in the tree.
154 /// </summary>
155 /// <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>
156 /// <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>
157 public void InsertAfter(SyntaxNode node, IEnumerable<SyntaxNode> newNodes)
158 {
159 CheckNodeInTree(node);
160 _changes.Add(new InsertChange(node, newNodes, isBefore: false));
161 }
162
163 /// <summary>
164 /// Insert the new node after the specified node already existing in the tree.
165 /// </summary>
166 /// <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>
167 /// <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>
168 public void InsertAfter(SyntaxNode node, SyntaxNode newNode)
169 {
170 CheckNodeInTree(node);
171 this.InsertAfter(node, new[] { newNode });
172 }
173
174 private void CheckNodeInTree(SyntaxNode node)
175 {
176 if (!_root.Contains(node))
177 {
178 throw new ArgumentException(Microsoft.CodeAnalysis.WorkspacesResources.TheNodeIsNotPartOfTheTree, nameof(node));
179 }
180 }
181
182 private abstract class Change
183 {
184 internal readonly SyntaxNode Node;
185
186 public Change(SyntaxNode node)
187 {
188 this.Node = node;
189 }
190
191 public abstract SyntaxNode Apply(SyntaxNode root, SyntaxGenerator generator);
192 }
193
194 private class NoChange : Change
195 {
196 public NoChange(SyntaxNode node)
197 : base(node)
198 {
199 }
200
201 public override SyntaxNode Apply(SyntaxNode root, SyntaxGenerator generator)
202 {
203 return root;
204 }
205 }
206
207 private class RemoveChange : Change
208 {
209 private readonly SyntaxRemoveOptions _options;
210
211 public RemoveChange(SyntaxNode node, SyntaxRemoveOptions options)
212 : base(node)
213 {
214 _options = options;
215 }
216
217 public override SyntaxNode Apply(SyntaxNode root, SyntaxGenerator generator)
218 {
219 return generator.RemoveNode(root, root.GetCurrentNode(this.Node), _options);
220 }
221 }
222
223 private class ReplaceChange : Change
224 {
225 private readonly Func<SyntaxNode, SyntaxGenerator, SyntaxNode> _modifier;
226
227 public ReplaceChange(SyntaxNode node, Func<SyntaxNode, SyntaxGenerator, SyntaxNode> modifier)
228 : base(node)
229 {
230 _modifier = modifier;
231 }
232
233 public override SyntaxNode Apply(SyntaxNode root, SyntaxGenerator generator)
234 {
235 var current = root.GetCurrentNode(this.Node);
236 var newNode = _modifier(current, generator);
237 return generator.ReplaceNode(root, current, newNode);
238 }
239 }
240
241 private class InsertChange : Change
242 {
243 private readonly List<SyntaxNode> _newNodes;
244 private readonly bool _isBefore;
245
246 public InsertChange(SyntaxNode node, IEnumerable<SyntaxNode> newNodes, bool isBefore)
247 : base(node)
248 {
249 _newNodes = newNodes.ToList();
250 _isBefore = isBefore;
251 }
252
253 public override SyntaxNode Apply(SyntaxNode root, SyntaxGenerator generator)
254 {
255 if (_isBefore)
256 {
257 return generator.InsertNodesBefore(root, root.GetCurrentNode(this.Node), _newNodes);
258 }
259 else
260 {
261 return generator.InsertNodesAfter(root, root.GetCurrentNode(this.Node), _newNodes);
262 }
263 }
264 }
265 }
266}