PageRenderTime 67ms CodeModel.GetById 44ms app.highlight 18ms RepoModel.GetById 1ms app.codeStats 0ms

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

https://gitlab.com/sharadag/Roslyn
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}