2
// FormattingVisitor.cs
5
// Mike KrĆ¼ger <mkrueger@xamarin.com>
7
// Copyright (c) 2010 Novell, Inc (http://www.novell.com)
8
// Copyright (c) 2013 Xamarin Inc. (http://xamarin.com)
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:
17
// The above copyright notice and this permission notice shall be included in
18
// all copies or substantial portions of the Software.
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
30
using ICSharpCode.NRefactory.Editor;
31
using ICSharpCode.NRefactory.TypeSystem;
32
using System.Threading;
34
namespace ICSharpCode.NRefactory.CSharp
36
[Obsolete("This class was replaced by CSharpFormatter.")]
37
public class AstFormattingVisitor {}
39
partial class FormattingVisitor : DepthFirstAstVisitor
41
readonly CSharpFormatter formatter;
42
readonly FormattingChanges changes;
43
readonly IDocument document;
44
readonly CancellationToken token;
48
public bool HadErrors {
54
CSharpFormattingOptions policy {
56
return formatter.Policy;
60
TextEditorOptions options {
62
return formatter.TextEditorOptions;
66
FormattingChanges.TextReplaceAction AddChange(int offset, int removedChars, string insertedText)
68
return changes.AddChange(offset, removedChars, insertedText);
71
public FormattingVisitor(CSharpFormatter formatter, IDocument document, FormattingChanges changes, CancellationToken token)
73
if (formatter == null)
74
throw new ArgumentNullException("formatter");
76
throw new ArgumentNullException("document");
78
throw new ArgumentNullException("changes");
80
this.formatter = formatter;
81
this.changes = changes;
82
this.document = document;
85
curIndent = new Indent(formatter.TextEditorOptions);
88
void VisitChildrenToFormat (AstNode parent, Action<AstNode> callback)
91
for (var child = parent.FirstChild; child != null; child = next) {
92
token.ThrowIfCancellationRequested();
93
// Store next to allow the loop to continue
94
// if the visitor removes/replaces child.
95
next = child.GetNextSibling(NoWhitespacePredicate);
97
if (formatter.FormattingRegions.Count > 0) {
98
if (formatter.FormattingRegions.Any(r => r.IsInside(child.StartLocation) || r.IsInside(child.EndLocation))) {
101
var childRegion = child.Region;
102
if (formatter.FormattingRegions.Any(r => childRegion.IsInside(r.Begin) || childRegion.IsInside(r.End)))
105
if (child.StartLocation > formatter.lastFormattingLocation)
113
protected override void VisitChildren (AstNode node)
115
VisitChildrenToFormat (node, n => n.AcceptVisitor (this));
118
public void EnsureNewLinesAfter(AstNode node, int blankLines)
120
if (formatter.FormattingMode != FormattingMode.Intrusive)
122
int foundBlankLines = 0;
123
var nextNode = node.GetNextNode ();
124
AstNode lastNewLine = null;
125
while (nextNode != null) {
126
if (!(nextNode is NewLineNode))
128
lastNewLine = nextNode;
130
nextNode = nextNode.GetNextNode ();
132
if (nextNode == null)
134
var start = document.GetOffset(node.EndLocation);
135
var end = document.GetOffset((lastNewLine ?? nextNode).StartLocation);
136
var sb = new StringBuilder(options.EolMarker.Length * blankLines);
137
for (int i = 0; i < blankLines + (lastNewLine != null ? -1 : 0); i++) {
138
sb.Append(options.EolMarker);
140
AddChange(start, end - start, sb.ToString());
143
public void EnsureBlankLinesBefore(AstNode node, int blankLines)
145
if (formatter.FormattingMode != FormattingMode.Intrusive)
147
var loc = node.StartLocation;
151
} while (line > 0 && IsSpacing(document.GetLineByNumber(line)));
152
int end = document.GetOffset(loc.Line, 1);
153
int start = document.GetOffset(line + 1, 1);
154
var sb = new StringBuilder ();
155
for (int i = 0; i < blankLines; i++) {
156
sb.Append(options.EolMarker);
158
if (end - start == 0 && sb.Length == 0)
160
AddChange(start, end - start, sb.ToString());
163
bool IsSimpleAccessor(Accessor accessor)
165
if (accessor.IsNull || accessor.Body.IsNull || accessor.Body.FirstChild == null) {
168
if (accessor.Body.Statements.Count() != 1) {
171
return !(accessor.Body.Statements.FirstOrDefault() is BlockStatement);
175
bool IsSpacing(char ch)
177
return ch == ' ' || ch == '\t';
180
bool IsSpacing(ISegment segment)
182
int endOffset = segment.EndOffset;
183
for (int i = segment.Offset; i < endOffset; i++) {
184
if (!IsSpacing(document.GetCharAt(i))) {
191
int SearchLastNonWsChar(int startOffset, int endOffset)
193
startOffset = Math.Max(0, startOffset);
194
endOffset = Math.Max(startOffset, endOffset);
195
if (startOffset >= endOffset) {
199
bool inComment = false;
201
for (int i = startOffset; i < endOffset && i < document.TextLength; i++) {
202
char ch = document.GetCharAt(i);
206
if (ch == '/' && i + 1 < document.TextLength && document.GetCharAt(i + 1) == '/') {
209
if (ch == '/' && i + 1 < document.TextLength && document.GetCharAt(i + 1) == '*') {
214
if (inComment && ch == '*' && i + 1 < document.TextLength && document.GetCharAt(i + 1) == '/') {
226
void ForceSpace(int startOffset, int endOffset, bool forceSpace)
228
int lastNonWs = SearchLastNonWsChar(startOffset, endOffset);
230
AddChange(lastNonWs + 1, Math.Max(0, endOffset - lastNonWs - 1), forceSpace ? " " : "");
233
void ForceSpacesAfter(AstNode n, bool forceSpaces)
238
TextLocation location = n.EndLocation;
239
int offset = document.GetOffset(location);
240
if (location.Column > document.GetLineByNumber(location.Line).Length) {
244
while (i < document.TextLength && IsSpacing (document.GetCharAt (i))) {
247
ForceSpace(offset - 1, i, forceSpaces);
250
int ForceSpacesBefore(AstNode n, bool forceSpaces)
252
if (n == null || n.IsNull) {
255
TextLocation location = n.StartLocation;
256
// respect manual line breaks.
257
if (location.Column <= 1 || GetIndentation(location.Line).Length == location.Column - 1) {
261
int offset = document.GetOffset(location);
263
while (i >= 0 && IsSpacing (document.GetCharAt (i))) {
266
ForceSpace(i, offset, forceSpaces);
270
int ForceSpacesBeforeRemoveNewLines(AstNode n, bool forceSpace = true)
272
if (n == null || n.IsNull) {
275
int offset = document.GetOffset(n.StartLocation);
278
char ch = document.GetCharAt(i);
279
if (!IsSpacing(ch) && ch != '\r' && ch != '\n')
283
var length = Math.Max(0, (offset - 1) - i);
284
AddChange(i + 1, length, forceSpace ? " " : "");
288
static bool NoWhitespacePredicate(AstNode arg)
290
return !(arg is NewLineNode || arg is WhitespaceNode);
293
static bool IsMember(AstNode nextSibling)
295
return nextSibling != null && nextSibling.NodeType == NodeType.Member;
298
static bool ShouldBreakLine(NewLinePlacement placement, CSharpTokenNode token)
300
if (placement == NewLinePlacement.NewLine)
302
if (placement == NewLinePlacement.SameLine)
304
var prevMeaningfulNode = token.GetPrevNode (n =>n.Role !=Roles.NewLine && n.Role != Roles.Whitespace && n.Role !=Roles.Comment);
305
return prevMeaningfulNode.EndLocation.Line != token.StartLocation.Line;
308
void ForceSpaceBefore(AstNode node, bool forceSpace)
310
_ForceSpaceBefore(document.GetOffset(node.StartLocation), forceSpace);
313
void _ForceSpaceBefore(int offset, bool forceSpace)
315
bool insertedSpace = false;
317
char ch = document.GetCharAt(offset);
318
//Console.WriteLine (ch);
319
if (!IsSpacing(ch) && (insertedSpace || !forceSpace)) {
322
if (ch == ' ' && forceSpace) {
324
AddChange(offset, 1, null);
326
insertedSpace = true;
328
} else if (forceSpace) {
329
if (!insertedSpace) {
330
AddChange(offset, IsSpacing(ch) ? 1 : 0, " ");
331
insertedSpace = true;
332
} else if (IsSpacing(ch)) {
333
AddChange(offset, 1, null);
338
} while (offset >= 0);
341
public void FixSemicolon(CSharpTokenNode semicolon)
343
if (semicolon.IsNull) {
346
int endOffset = document.GetOffset(semicolon.StartLocation);
347
int offset = endOffset;
348
while (offset - 1 > 0 && char.IsWhiteSpace (document.GetCharAt (offset - 1))) {
351
if (offset < endOffset) {
352
AddChange(offset, endOffset - offset, null);
356
void PlaceOnNewLine(NewLinePlacement newLine, AstNode keywordNode)
358
if (keywordNode == null || newLine == NewLinePlacement.DoNotCare) {
362
var prev = keywordNode.GetPrevNode ();
363
if (prev is Comment || prev is PreProcessorDirective)
366
int offset = document.GetOffset(keywordNode.StartLocation);
368
int whitespaceStart = SearchWhitespaceStart(offset);
369
string indentString = newLine == NewLinePlacement.NewLine ? options.EolMarker + curIndent.IndentString : " ";
370
AddChange(whitespaceStart, offset - whitespaceStart, indentString);
373
string nextStatementIndent;
375
void FixStatementIndentation(TextLocation location)
377
if (location.Line < 1 || location.Column < 1) {
378
Console.WriteLine("invalid location!");
381
int offset = document.GetOffset(location);
383
Console.WriteLine("possible wrong offset");
384
Console.WriteLine(Environment.StackTrace);
387
bool isEmpty = IsLineIsEmptyUpToEol(offset);
388
int lineStart = SearchWhitespaceLineStart(offset);
389
string indentString = nextStatementIndent ?? (isEmpty ? "" : options.EolMarker) + curIndent.IndentString;
390
nextStatementIndent = null;
391
AddChange(lineStart, offset - lineStart, indentString);
394
void FixIndentation (AstNode node)
396
FixIndentation(node.StartLocation, 0);
399
void FixIndentation(TextLocation location, int relOffset)
401
if (location.Line < 1 || location.Line > document.LineCount) {
402
Console.WriteLine("Invalid location " + location);
403
Console.WriteLine(Environment.StackTrace);
407
string lineIndent = GetIndentation(location.Line);
408
string indentString = curIndent.IndentString;
409
if (indentString != lineIndent && location.Column - 1 + relOffset == lineIndent.Length) {
410
AddChange(document.GetOffset(location.Line, 1), lineIndent.Length, indentString);
414
void FixIndentationForceNewLine(AstNode node)
416
if (node.GetPrevNode () is NewLineNode) {
417
FixIndentation(node);
419
int offset = document.GetOffset(node.StartLocation);
420
AddChange(offset, 0, curIndent.IndentString);
424
string GetIndentation(int lineNumber)
426
var line = document.GetLineByNumber(lineNumber);
427
var b = new StringBuilder ();
428
int endOffset = line.EndOffset;
429
for (int i = line.Offset; i < endOffset; i++) {
430
char c = document.GetCharAt(i);
440
void FixOpenBrace(BraceStyle braceStyle, AstNode lbrace)
444
switch (braceStyle) {
445
case BraceStyle.DoNotChange:
448
case BraceStyle.BannerStyle:
449
case BraceStyle.EndOfLine:
450
var prev = lbrace.GetPrevNode();
451
while (prev is NewLineNode)
452
prev = prev.GetPrevNode();
453
if (prev is PreProcessorDirective)
455
int prevOffset = document.GetOffset(prev.EndLocation);
457
if (prev is Comment || prev is PreProcessorDirective) {
458
int next = document.GetOffset(lbrace.GetNextNode ().StartLocation);
459
AddChange(prevOffset, next - prevOffset, "");
460
while (prev is Comment || prev is PreProcessorDirective)
461
prev = prev.GetPrevNode();
462
prevOffset = document.GetOffset(prev.EndLocation);
463
AddChange(prevOffset, 0, " {");
465
int braceOffset2 = document.GetOffset(lbrace.StartLocation);
466
AddChange(prevOffset, braceOffset2 - prevOffset, " ");
469
case BraceStyle.EndOfLineWithoutSpace:
470
prev = lbrace.GetPrevNode();
471
while (prev is NewLineNode)
472
prev = prev.GetPrevNode();
473
if (prev is PreProcessorDirective)
475
prevOffset = document.GetOffset(prev.EndLocation);
476
int braceOffset = document.GetOffset(lbrace.StartLocation);
477
AddChange(prevOffset, braceOffset - prevOffset, "");
480
case BraceStyle.NextLine:
481
prev = lbrace.GetPrevNode();
482
while (prev is NewLineNode)
483
prev = prev.GetPrevNode();
484
if (prev is PreProcessorDirective)
486
prevOffset = document.GetOffset(prev.EndLocation);
487
braceOffset = document.GetOffset(lbrace.StartLocation);
488
AddChange(prevOffset, braceOffset - prevOffset, options.EolMarker + curIndent.IndentString);
490
case BraceStyle.NextLineShifted:
491
prev = lbrace.GetPrevNode();
492
while (prev is NewLineNode)
493
prev = prev.GetPrevNode();
494
if (prev is PreProcessorDirective)
496
prevOffset = document.GetOffset(prev.EndLocation);
497
braceOffset = document.GetOffset(lbrace.StartLocation);
498
curIndent.Push(IndentType.Block);
499
AddChange(prevOffset, braceOffset - prevOffset, options.EolMarker + curIndent.IndentString);
502
case BraceStyle.NextLineShifted2:
503
prev = lbrace.GetPrevNode();
504
while (prev is NewLineNode)
505
prev = prev.GetPrevNode();
506
if (prev is PreProcessorDirective)
508
prevOffset = document.GetOffset(prev.EndLocation);
509
braceOffset = document.GetOffset(lbrace.StartLocation);
510
curIndent.Push(IndentType.Block);
511
AddChange(prevOffset, braceOffset - prevOffset, options.EolMarker + curIndent.IndentString);
517
void CorrectClosingBrace (AstNode rbrace)
519
int braceOffset = document.GetOffset(rbrace.StartLocation);
520
var prevNode = rbrace.GetPrevNode();
521
int prevNodeOffset = prevNode != null ? document.GetOffset(prevNode.EndLocation) : 0;
522
if (prevNode is NewLineNode) {
523
AddChange(prevNodeOffset, braceOffset - prevNodeOffset, curIndent.IndentString);
525
AddChange(prevNodeOffset, braceOffset - prevNodeOffset, options.EolMarker + curIndent.IndentString);
529
void FixClosingBrace(BraceStyle braceStyle, AstNode rbrace)
533
switch (braceStyle) {
534
case BraceStyle.DoNotChange:
537
case BraceStyle.NextLineShifted:
538
case BraceStyle.BannerStyle:
539
curIndent.Push(IndentType.Block);
540
CorrectClosingBrace (rbrace);
543
case BraceStyle.EndOfLineWithoutSpace:
544
case BraceStyle.EndOfLine:
545
case BraceStyle.NextLine:
546
CorrectClosingBrace (rbrace);
549
case BraceStyle.NextLineShifted2:
550
curIndent.Push(IndentType.Block);
551
CorrectClosingBrace (rbrace);