Free cookie consent management tool by TermsFeed Policy Generator

source: stable/HeuristicLab.ExtLibs/HeuristicLab.NRefactory/5.5.0/NRefactory.CSharp-5.5.0/Formatter/FormattingVisitor.cs @ 13400

Last change on this file since 13400 was 11700, checked in by jkarder, 9 years ago

#2077: created branch and added first version

File size: 20.5 KB
Line 
1//
2// FormattingVisitor.cs
3//
4// Author:
5//       Mike Krüger <mkrueger@xamarin.com>
6//
7// Copyright (c) 2010 Novell, Inc (http://www.novell.com)
8// Copyright (c) 2013 Xamarin Inc. (http://xamarin.com)
9//
10// Permission is hereby granted, free of charge, to any person obtaining a copy
11// of this software and associated documentation files (the "Software"), to deal
12// in the Software without restriction, including without limitation the rights
13// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14// copies of the Software, and to permit persons to whom the Software is
15// furnished to do so, subject to the following conditions:
16//
17// The above copyright notice and this permission notice shall be included in
18// all copies or substantial portions of the Software.
19//
20// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26// THE SOFTWARE.
27using System;
28using System.Text;
29using System.Linq;
30using ICSharpCode.NRefactory.Editor;
31using ICSharpCode.NRefactory.TypeSystem;
32using System.Threading;
33using System.Collections.Generic;
34using ICSharpCode.NRefactory.Utils;
35
36namespace ICSharpCode.NRefactory.CSharp
37{
38  [Obsolete("This class was replaced by CSharpFormatter.")]
39  public class AstFormattingVisitor {}
40
41  partial class FormattingVisitor 
42  {
43    readonly CSharpFormatter formatter;
44    readonly FormattingChanges changes;
45    readonly IDocument document;
46    readonly CancellationToken token;
47
48    Indent curIndent;
49
50    public bool HadErrors {
51      get;
52      set;
53    }
54   
55
56    CSharpFormattingOptions policy {
57      get {
58        return formatter.Policy;
59      }
60    }
61
62    TextEditorOptions options {
63      get {
64        return formatter.TextEditorOptions;
65      }
66    }
67
68    FormattingChanges.TextReplaceAction AddChange(int offset, int removedChars, string insertedText)
69    {
70      return changes.AddChange(offset, removedChars, insertedText);
71    }
72
73    public FormattingVisitor(CSharpFormatter formatter, IDocument document, FormattingChanges changes, CancellationToken token)
74    {
75      if (formatter == null)
76        throw new ArgumentNullException("formatter");
77      if (document == null)
78        throw new ArgumentNullException("document");
79      if (changes == null)
80        throw new ArgumentNullException("changes");
81   
82      this.formatter = formatter;
83      this.changes = changes;
84      this.document = document;
85      this.token = token;
86
87      curIndent = new Indent(formatter.TextEditorOptions);
88    }
89   
90    void VisitChildrenToFormat (AstNode parent, Action<AstNode> callback)
91    {
92      AstNode next;
93      for (var child = parent.FirstChild; child != null; child = next) {
94        token.ThrowIfCancellationRequested();
95        // Store next to allow the loop to continue
96        // if the visitor removes/replaces child.
97        next = child.GetNextSibling(NoWhitespacePredicate);
98       
99        if (formatter.FormattingRegions.Count > 0) {
100          if (formatter.FormattingRegions.Any(r => r.IsInside(child.StartLocation) || r.IsInside(child.EndLocation))) {
101            callback(child);
102          } else {
103            var childRegion = child.Region;
104            if (formatter.FormattingRegions.Any(r => childRegion.IsInside(r.Begin) || childRegion.IsInside(r.End)))
105                callback(child);
106          }
107          if (child.StartLocation > formatter.lastFormattingLocation)
108            break;
109        } else {
110          callback(child);
111        }
112      }
113    }
114   
115    protected override void VisitChildren (AstNode node)
116    {
117      VisitChildrenToFormat (node, n => n.AcceptVisitor (this));
118    }
119
120    #region NewLines
121
122    void AdjustNewLineBlock(AstNode startNode, int targetMinimumNewLineCount)
123    {
124      var indentString = policy.EmptyLineFormatting == EmptyLineFormatting.Indent ? curIndent.IndentString : "";
125
126      TextLocation newLineInsertPosition = startNode.EndLocation;
127      var node = startNode.NextSibling;
128      int currentNewLineCount = 0;
129      // Check the existing newlines
130      for (; currentNewLineCount < targetMinimumNewLineCount; node = node.NextSibling) {
131        if (node is WhitespaceNode)
132          continue;
133        if (!(node is NewLineNode))
134          break;
135        newLineInsertPosition = node.EndLocation;
136        currentNewLineCount++;
137        if (policy.EmptyLineFormatting == EmptyLineFormatting.DoNotChange) {
138          if (node.NextSibling == null)
139            // end of file/block etc, nothing more to do but break before assigning null to node
140            break;
141          continue;
142        }
143        var isBlankLine = IsSpacing(document.GetLineByNumber(node.StartLocation.Line));
144        if (!isBlankLine) {
145          // remove EOL whitespace if appropriate
146          if (policy.RemoveEndOfLineWhiteSpace) {
147            var offset = document.GetOffset(node.StartLocation);
148            var start = SearchWhitespaceStart(offset);
149            if (start != offset)
150              AddChange(start, offset - start, null);
151          }
152        } else {
153          var actualIndent = GetIndentation(node.StartLocation.Line);
154          if (actualIndent != indentString) {
155            var start = document.GetOffset(new TextLocation(node.StartLocation.Line, 0));
156            AddChange(start, actualIndent.Length, indentString);
157          }
158        }
159        if (node.NextSibling == null)
160          // end of file/block etc, nothing more to do but break before assigning null to node
161          break;
162      }
163      if (currentNewLineCount < targetMinimumNewLineCount) {
164        // We need to add more newlines
165        var builder = new StringBuilder();
166        for (; currentNewLineCount < targetMinimumNewLineCount; currentNewLineCount++) {
167          if (currentNewLineCount > 0)
168            // Don't indent the first line in the block since that is not an empty line.
169            builder.Append(indentString);
170          builder.Append(options.EolMarker);
171        }
172        var offset = document.GetOffset(newLineInsertPosition);
173        if (offset >= 0)
174          AddChange(offset, 0, builder.ToString());
175      } else if (currentNewLineCount == targetMinimumNewLineCount && node is NewLineNode){
176//        // Check to see if there are any newlines to remove
177//        var endNode = node.GetNextSibling(n => !(n is NewLineNode || n is WhitespaceNode));
178//        if (endNode != null) {
179//          var startOffset = document.GetOffset(newLineInsertPosition);
180//          var endOffset = document.GetOffset(new TextLocation(endNode.StartLocation.Line, 0));
181//          EnsureText(startOffset, endOffset, null);
182//        }
183      }
184    }
185
186    public void EnsureMinimumNewLinesAfter(AstNode node, int blankLines)
187    {
188      if (node is PreProcessorDirective) {
189        var directive = (PreProcessorDirective)node;
190        if (directive.Type == PreProcessorDirectiveType.Pragma)
191          return;
192      }
193      if (blankLines < 0)
194        return;
195      if (formatter.FormattingMode != FormattingMode.Intrusive)
196        blankLines = Math.Min(1, blankLines);
197      AdjustNewLineBlock(node, blankLines);
198    }
199   
200    public void EnsureMinimumBlankLinesBefore(AstNode node, int blankLines)
201    {
202      if (formatter.FormattingMode != FormattingMode.Intrusive)
203        return;
204      var loc = node.StartLocation;
205      int line = loc.Line;
206      do {
207        line--;
208      } while (line > 0 && IsSpacing(document.GetLineByNumber(line)));
209      if (line > 0 && !IsSpacing(document.GetLineByNumber(line)))
210          line++;
211
212      if (loc.Line - line >= blankLines)
213        return;
214
215      var sb = new StringBuilder ();
216      for (int i = 0; i < blankLines; i++)
217        sb.Append(options.EolMarker);
218      int end = document.GetOffset(loc.Line, 1);
219      if (loc.Line == line) {
220        AddChange(end, 0, sb.ToString());
221        return;
222      }
223      if (line + 1 > document.LineCount)
224        return;
225      int start = document.GetOffset(line + 1, 1);
226      if (end - start <= 0 && sb.Length == 0)
227        return;
228      AddChange(start, end - start, sb.ToString());
229    }
230
231    #endregion
232
233    bool IsSimpleAccessor(Accessor accessor)
234    {
235      if (accessor.IsNull || accessor.Body.IsNull || accessor.Body.FirstChild == null) {
236        return true;
237      }
238      var firstStatement = accessor.Body.Statements.FirstOrDefault();
239      if (firstStatement == null)
240        return true;
241
242      if (!(firstStatement is ReturnStatement || firstStatement is ExpressionStatement|| firstStatement is EmptyStatement || firstStatement is ThrowStatement))
243        return false;
244
245      if (firstStatement.GetNextSibling(s => s.Role == BlockStatement.StatementRole) != null)
246        return false;
247
248      return !(accessor.Body.Statements.FirstOrDefault() is BlockStatement);
249    }
250   
251    static bool IsSpacing(char ch)
252    {
253      return ch == ' ' || ch == '\t';
254    }
255   
256    bool IsSpacing(ISegment segment)
257    {
258      int endOffset = segment.EndOffset;
259      for (int i = segment.Offset; i < endOffset; i++) {
260        if (!IsSpacing(document.GetCharAt(i))) {
261          return false;
262        }
263      }
264      return true;
265    }
266   
267    int SearchLastNonWsChar(int startOffset, int endOffset)
268    {
269      startOffset = Math.Max(0, startOffset);
270      endOffset = Math.Max(startOffset, endOffset);
271      if (startOffset >= endOffset) {
272        return startOffset;
273      }
274      int result = -1;
275      bool inComment = false;
276     
277      for (int i = startOffset; i < endOffset && i < document.TextLength; i++) {
278        char ch = document.GetCharAt(i);
279        if (IsSpacing(ch)) {
280          continue;
281        }
282        if (ch == '/' && i + 1 < document.TextLength && document.GetCharAt(i + 1) == '/') {
283          return result;
284        }
285        if (ch == '/' && i + 1 < document.TextLength && document.GetCharAt(i + 1) == '*') {
286          inComment = true;
287          i++;
288          continue;
289        }
290        if (inComment && ch == '*' && i + 1 < document.TextLength && document.GetCharAt(i + 1) == '/') {
291          inComment = false;
292          i++;
293          continue;
294        }
295        if (!inComment) {
296          result = i;
297        }
298      }
299      return result;
300    }
301   
302    void ForceSpace(int startOffset, int endOffset, bool forceSpace)
303    {
304      int lastNonWs = SearchLastNonWsChar(startOffset, endOffset);
305      if (lastNonWs < 0)
306        return;
307
308      var spaceCount = Math.Max(0, endOffset - lastNonWs - 1);
309      if (forceSpace) {
310        if (spaceCount != 1) {
311          // Here we could technically remove spaceCount - 1 chars instead
312          // and skip replacing that with new space, but we want to trigger the
313          // overlap detection if this space is changed again for some reason
314          AddChange(lastNonWs + 1, spaceCount, " ");
315        }
316      } else if (spaceCount > 0 && !forceSpace) {
317        AddChange(lastNonWs + 1, spaceCount, "");
318      }
319    }
320   
321    void ForceSpacesAfter(AstNode n, bool forceSpaces)
322    {
323      if (n == null) {
324        return;
325      }
326      TextLocation location = n.EndLocation;
327      int offset = document.GetOffset(location);
328      if (location.Column > document.GetLineByNumber(location.Line).Length) {
329        return;
330      }
331      int i = offset;
332      while (i < document.TextLength && IsSpacing (document.GetCharAt (i))) {
333        i++;
334      }
335      ForceSpace(offset - 1, i, forceSpaces);
336    }
337
338    int ForceSpacesBefore(AstNode n, bool forceSpaces)
339    {
340      if (n == null || n.IsNull) {
341        return 0;
342      }
343      TextLocation location = n.StartLocation;
344      // respect manual line breaks.
345      if (location.Column <= 1 || GetIndentation(location.Line).Length == location.Column - 1) {
346        return 0;
347      }
348     
349      int offset = document.GetOffset(location);
350      int i = offset - 1;
351      while (i >= 0 && IsSpacing (document.GetCharAt (i))) {
352        i--;
353      }
354      ForceSpace(i, offset, forceSpaces);
355      return i;
356    }
357   
358    int ForceSpacesBeforeRemoveNewLines(AstNode n, bool forceSpace = true)
359    {
360      if (n == null || n.IsNull) {
361        return 0;
362      }
363      int offset = document.GetOffset(n.StartLocation);
364      int i = offset - 1;
365      while (i >= 0) {
366        char ch = document.GetCharAt(i);
367        if (!IsSpacing(ch) && ch != '\r' && ch != '\n')
368          break;
369        i--;
370      }
371      var length = Math.Max(0, (offset - 1) - i);
372      AddChange(i + 1, length, forceSpace ? " " : "");
373      return i;
374    }
375
376    internal static bool NoWhitespacePredicate(AstNode arg)
377    {
378      return !(arg is NewLineNode || arg is WhitespaceNode);
379    }
380
381    static bool IsMember(AstNode nextSibling)
382    {
383      return nextSibling != null && nextSibling.NodeType == NodeType.Member;
384    }
385
386    static bool ShouldBreakLine(NewLinePlacement placement, CSharpTokenNode token)
387    {
388      if (placement == NewLinePlacement.NewLine)
389        return true;
390      if (placement == NewLinePlacement.SameLine)
391        return false;
392      if (token.IsNull)
393        return false;
394      var prevMeaningfulNode = token.GetPrevNode (n =>n.Role !=Roles.NewLine && n.Role != Roles.Whitespace && n.Role !=Roles.Comment);
395      return prevMeaningfulNode.EndLocation.Line != token.StartLocation.Line;
396    }
397
398    void ForceSpaceBefore(AstNode node, bool forceSpace)
399    {
400      var offset = document.GetOffset(node.StartLocation);
401      int end = offset;
402      // ForceSpace inserts a space one char after start in the case of a missing space
403      // Therefore, make sure that start < offset by starting at offset - 1
404      int start = SearchWhitespaceStart(offset - 1);
405      ForceSpace(start, end, forceSpace);
406    }
407
408    public void FixSemicolon(CSharpTokenNode semicolon)
409    {
410      if (semicolon.IsNull)
411        return;
412      int endOffset = document.GetOffset(semicolon.StartLocation);
413      int offset = endOffset;
414      while (offset - 1 > 0 && char.IsWhiteSpace (document.GetCharAt (offset - 1))) {
415        offset--;
416      }
417      if (policy.SpaceBeforeSemicolon) {
418        AddChange(offset, endOffset - offset, " ");
419      } else {
420        if (offset < endOffset)
421          AddChange(offset, endOffset - offset, null);
422      }
423    }
424   
425    void PlaceOnNewLine(NewLinePlacement newLine, AstNode keywordNode)
426    {
427      if (keywordNode == null || keywordNode.StartLocation.IsEmpty)
428        return;
429     
430      var prev = keywordNode.GetPrevNode (NoWhitespacePredicate);
431      if (prev is Comment || prev is PreProcessorDirective)
432        return;
433
434      if (newLine == NewLinePlacement.DoNotCare)
435        newLine = prev.EndLocation.Line == keywordNode.StartLocation.Line ? NewLinePlacement.SameLine : NewLinePlacement.NewLine;
436
437      int offset = document.GetOffset(keywordNode.StartLocation);
438     
439      int whitespaceStart = SearchWhitespaceStart(offset);
440      string indentString = newLine == NewLinePlacement.NewLine ? options.EolMarker + curIndent.IndentString : " ";
441      AddChange(whitespaceStart, offset - whitespaceStart, indentString);
442    }
443   
444    string nextStatementIndent;
445   
446    void FixStatementIndentation(TextLocation location)
447    {
448      if (location.Line < 1 || location.Column < 1) {
449        Console.WriteLine("invalid location!");
450        return;
451      }
452      int offset = document.GetOffset(location);
453      if (offset <= 0) {
454        Console.WriteLine("possible wrong offset");
455        Console.WriteLine(Environment.StackTrace);
456        return;
457      }
458      bool isEmpty = IsLineIsEmptyUpToEol(offset);
459      int lineStart = SearchWhitespaceLineStart(offset);
460      string indentString = nextStatementIndent ?? (isEmpty ? "" : options.EolMarker) + curIndent.IndentString;
461      nextStatementIndent = null;
462      EnsureText(lineStart, offset, indentString);
463    }
464
465    void FixIndentation (AstNode node)
466    {
467      FixIndentation(node.StartLocation, 0);
468    }
469   
470    void FixIndentation(TextLocation location, int relOffset)
471    {
472      if (location.Line < 1 || location.Line > document.LineCount) {
473        Console.WriteLine("Invalid location " + location);
474        Console.WriteLine(Environment.StackTrace);
475        return;
476      }
477     
478      string lineIndent = GetIndentation(location.Line);
479      string indentString = curIndent.IndentString;
480      if (indentString != lineIndent && location.Column - 1 + relOffset == lineIndent.Length) {
481        AddChange(document.GetOffset(location.Line, 1), lineIndent.Length, indentString);
482      }
483    }
484   
485    void FixIndentationForceNewLine(AstNode node)
486    {
487      var directive = node as PreProcessorDirective;
488      if (node.GetPrevNode () is NewLineNode) {
489        if (directive != null && !policy.IndentPreprocessorDirectives) {
490          var startNode = node.GetPrevNode ();
491          var startOffset = document.GetOffset(startNode.EndLocation);
492          int endOffset = document.GetOffset(node.StartLocation);
493          AddChange(startOffset, endOffset - startOffset, "");
494          return;
495        } else {
496          FixIndentation(node);
497        }
498      } else {
499        // if no new line preceeds an #endif directive it's excluded
500        if (directive != null) {
501          if (directive.Type == PreProcessorDirectiveType.Endif)
502            return;
503        }
504        var startNode = node.GetPrevSibling(n => !(n is WhitespaceNode)) ?? node;
505        var startOffset = document.GetOffset(startNode.EndLocation);
506        int endOffset = document.GetOffset(node.StartLocation);
507        if (startOffset >= endOffset)
508          return;
509        if (directive != null && !policy.IndentPreprocessorDirectives) {
510          AddChange(startOffset, endOffset - startOffset, "");
511          return;
512        }
513
514        AddChange(startOffset, endOffset - startOffset, curIndent.IndentString);
515      }
516    }
517
518    string GetIndentation(int lineNumber)
519    {
520      var line = document.GetLineByNumber(lineNumber);
521      var b = new StringBuilder ();
522      int endOffset = line.EndOffset;
523      for (int i = line.Offset; i < endOffset; i++) {
524        char c = document.GetCharAt(i);
525        if (!IsSpacing(c)) {
526          break;
527        }
528        b.Append(c);
529      }
530      return b.ToString();
531    }
532
533    void EnsureText(int start, int end, string replacementText)
534    {
535      var length = end - start;
536      if (length == 0 && string.IsNullOrEmpty(replacementText))
537        return;
538      if (replacementText == null || replacementText.Length != length) {
539        AddChange(start, length, replacementText);
540        return;
541      }
542      for (int i = 0; i < length; i++) {
543        if (document.GetCharAt(start + i) != replacementText[i]) {
544          AddChange(start, length, replacementText);
545          break;
546        }
547      }
548    }
549   
550    void FixOpenBrace(BraceStyle braceStyle, AstNode lbrace)
551    {
552      if (lbrace.IsNull)
553        return;
554      switch (braceStyle) {
555        case BraceStyle.DoNotChange:
556          return;
557
558        case BraceStyle.BannerStyle:
559        case BraceStyle.EndOfLine:
560          var prev = lbrace.GetPrevNode (NoWhitespacePredicate);
561          if (prev is PreProcessorDirective)
562            return;
563          int prevOffset = document.GetOffset(prev.EndLocation);
564
565          if (prev is Comment || prev is PreProcessorDirective) {
566            int next = document.GetOffset(lbrace.GetNextNode ().StartLocation);
567            EnsureText(prevOffset, next, "");
568            while (prev is Comment || prev is PreProcessorDirective)
569              prev = prev.GetPrevNode();
570            prevOffset = document.GetOffset(prev.EndLocation);
571            AddChange(prevOffset, 0, " {");
572          } else {
573            int braceOffset2 = document.GetOffset(lbrace.StartLocation);
574            EnsureText(prevOffset, braceOffset2, " ");
575          }
576          break;
577        case BraceStyle.EndOfLineWithoutSpace:
578          prev = lbrace.GetPrevNode (NoWhitespacePredicate);
579          if (prev is PreProcessorDirective)
580            return;
581          prevOffset = document.GetOffset(prev.EndLocation);
582          int braceOffset = document.GetOffset(lbrace.StartLocation);
583          EnsureText(prevOffset, braceOffset, "");
584          break;
585
586        case BraceStyle.NextLine:
587          prev = lbrace.GetPrevNode (NoWhitespacePredicate);
588          if (prev is PreProcessorDirective)
589            return;
590          prevOffset = document.GetOffset(prev.EndLocation);
591          braceOffset = document.GetOffset(lbrace.StartLocation);
592          EnsureText(prevOffset, braceOffset, options.EolMarker + curIndent.IndentString);
593          break;
594        case BraceStyle.NextLineShifted:
595          prev = lbrace.GetPrevNode (NoWhitespacePredicate);
596          if (prev is PreProcessorDirective)
597            return;
598          prevOffset = document.GetOffset(prev.EndLocation);
599          braceOffset = document.GetOffset(lbrace.StartLocation);
600          curIndent.Push(IndentType.Block);
601          EnsureText(prevOffset, braceOffset, options.EolMarker + curIndent.IndentString);
602          curIndent.Pop();
603          break;
604        case BraceStyle.NextLineShifted2:
605          prev = lbrace.GetPrevNode (NoWhitespacePredicate);
606          if (prev is PreProcessorDirective)
607            return;
608          prevOffset = document.GetOffset(prev.EndLocation);
609          braceOffset = document.GetOffset(lbrace.StartLocation);
610          curIndent.Push(IndentType.Block);
611          EnsureText(prevOffset, braceOffset, options.EolMarker + curIndent.IndentString);
612          curIndent.Pop();
613          break;
614      }
615    }
616
617    void CorrectClosingBrace (AstNode rbrace)
618    {
619      if (rbrace.IsNull)
620        return;
621      int braceOffset = document.GetOffset(rbrace.StartLocation);
622      var prevNode = rbrace.GetPrevNode();
623      int prevNodeOffset = prevNode != null ? document.GetOffset(prevNode.EndLocation) : 0;
624      if (prevNode is NewLineNode) {
625        EnsureText(prevNodeOffset, braceOffset, curIndent.IndentString);
626      } else {
627        EnsureText(prevNodeOffset, braceOffset, options.EolMarker + curIndent.IndentString);
628      }
629    }
630
631    void FixClosingBrace(BraceStyle braceStyle, AstNode rbrace)
632    {
633      if (rbrace.IsNull)
634        return;
635      switch (braceStyle) {
636        case BraceStyle.DoNotChange:
637          return;
638
639        case BraceStyle.NextLineShifted:
640        case BraceStyle.BannerStyle:
641          curIndent.Push(IndentType.Block);
642          CorrectClosingBrace (rbrace);
643          curIndent.Pop ();
644          break;
645        case BraceStyle.EndOfLineWithoutSpace:
646        case BraceStyle.EndOfLine:
647        case BraceStyle.NextLine:
648          CorrectClosingBrace (rbrace);
649          break;
650
651        case BraceStyle.NextLineShifted2:
652          curIndent.Push(IndentType.Block);
653          CorrectClosingBrace (rbrace);
654          curIndent.Pop ();
655          break;
656      }
657
658    }
659
660  }
661}
662
Note: See TracBrowser for help on using the repository browser.