#develop (short for SharpDevelop) is a free IDE for .NET programming languages.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

662 lines
21 KiB

//
// FormattingVisitor.cs
//
// Author:
// Mike Krüger <mkrueger@xamarin.com>
//
// Copyright (c) 2010 Novell, Inc (http://www.novell.com)
// Copyright (c) 2013 Xamarin Inc. (http://xamarin.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
using System;
using System.Text;
using System.Linq;
using ICSharpCode.NRefactory.Editor;
using ICSharpCode.NRefactory.TypeSystem;
using System.Threading;
namespace ICSharpCode.NRefactory.CSharp
{
[Obsolete("This class was replaced by CSharpFormatter.")]
public class AstFormattingVisitor {}
partial class FormattingVisitor : DepthFirstAstVisitor
{
readonly CSharpFormatter formatter;
readonly FormattingChanges changes;
readonly IDocument document;
readonly CancellationToken token;
Indent curIndent;
public bool HadErrors {
get;
set;
}
CSharpFormattingOptions policy {
get {
return formatter.Policy;
}
}
TextEditorOptions options {
get {
return formatter.TextEditorOptions;
}
}
FormattingChanges.TextReplaceAction AddChange(int offset, int removedChars, string insertedText)
{
return changes.AddChange(offset, removedChars, insertedText);
}
public FormattingVisitor(CSharpFormatter formatter, IDocument document, FormattingChanges changes, CancellationToken token)
{
if (formatter == null)
throw new ArgumentNullException("formatter");
if (document == null)
throw new ArgumentNullException("document");
if (changes == null)
throw new ArgumentNullException("changes");
this.formatter = formatter;
this.changes = changes;
this.document = document;
this.token = token;
curIndent = new Indent(formatter.TextEditorOptions);
}
void VisitChildrenToFormat (AstNode parent, Action<AstNode> callback)
{
AstNode next;
for (var child = parent.FirstChild; child != null; child = next) {
token.ThrowIfCancellationRequested();
// Store next to allow the loop to continue
// if the visitor removes/replaces child.
next = child.GetNextSibling(NoWhitespacePredicate);
if (formatter.FormattingRegions.Count > 0) {
if (formatter.FormattingRegions.Any(r => r.IsInside(child.StartLocation) || r.IsInside(child.EndLocation))) {
callback(child);
} else {
var childRegion = child.Region;
if (formatter.FormattingRegions.Any(r => childRegion.IsInside(r.Begin) || childRegion.IsInside(r.End)))
callback(child);
}
if (child.StartLocation > formatter.lastFormattingLocation)
break;
} else {
callback(child);
}
}
}
protected override void VisitChildren (AstNode node)
{
VisitChildrenToFormat (node, n => n.AcceptVisitor (this));
}
public void EnsureNewLinesAfter(AstNode node, int blankLines)
{
if (formatter.FormattingMode != FormattingMode.Intrusive)
blankLines = 1;
int foundBlankLines = 0;
var nextNode = node.GetNextNode ();
AstNode lastNewLine = null;
while (nextNode != null) {
if (!(nextNode is NewLineNode))
break;
lastNewLine = nextNode;
foundBlankLines++;
nextNode = nextNode.GetNextNode ();
}
if (nextNode == null)
return;
var start = document.GetOffset(node.EndLocation);
var end = document.GetOffset((lastNewLine ?? nextNode).StartLocation);
var sb = new StringBuilder(options.EolMarker.Length * blankLines);
for (int i = 0; i < blankLines + (lastNewLine != null ? -1 : 0); i++) {
sb.Append(options.EolMarker);
}
AddChange(start, end - start, sb.ToString());
}
public void EnsureBlankLinesBefore(AstNode node, int blankLines)
{
if (formatter.FormattingMode != FormattingMode.Intrusive)
return;
var loc = node.StartLocation;
int line = loc.Line;
do {
line--;
} while (line > 0 && IsSpacing(document.GetLineByNumber(line)));
int end = document.GetOffset(loc.Line, 1);
int start = document.GetOffset(line + 1, 1);
var sb = new StringBuilder ();
for (int i = 0; i < blankLines; i++) {
sb.Append(options.EolMarker);
}
if (end - start == 0 && sb.Length == 0)
return;
AddChange(start, end - start, sb.ToString());
}
bool IsSimpleAccessor(Accessor accessor)
{
if (accessor.IsNull || accessor.Body.IsNull || accessor.Body.FirstChild == null) {
return true;
}
if (accessor.Body.Statements.Count() != 1) {
return false;
}
return !(accessor.Body.Statements.FirstOrDefault() is BlockStatement);
}
bool IsSpacing(char ch)
{
return ch == ' ' || ch == '\t';
}
bool IsSpacing(ISegment segment)
{
int endOffset = segment.EndOffset;
for (int i = segment.Offset; i < endOffset; i++) {
if (!IsSpacing(document.GetCharAt(i))) {
return false;
}
}
return true;
}
int SearchLastNonWsChar(int startOffset, int endOffset)
{
startOffset = Math.Max(0, startOffset);
endOffset = Math.Max(startOffset, endOffset);
if (startOffset >= endOffset) {
return startOffset;
}
int result = -1;
bool inComment = false;
for (int i = startOffset; i < endOffset && i < document.TextLength; i++) {
char ch = document.GetCharAt(i);
if (IsSpacing(ch)) {
continue;
}
if (ch == '/' && i + 1 < document.TextLength && document.GetCharAt(i + 1) == '/') {
return result;
}
if (ch == '/' && i + 1 < document.TextLength && document.GetCharAt(i + 1) == '*') {
inComment = true;
i++;
continue;
}
if (inComment && ch == '*' && i + 1 < document.TextLength && document.GetCharAt(i + 1) == '/') {
inComment = false;
i++;
continue;
}
if (!inComment) {
result = i;
}
}
return result;
}
void ForceSpace(int startOffset, int endOffset, bool forceSpace)
{
int lastNonWs = SearchLastNonWsChar(startOffset, endOffset);
if (lastNonWs >= 0)
AddChange(lastNonWs + 1, Math.Max(0, endOffset - lastNonWs - 1), forceSpace ? " " : "");
}
void ForceSpacesAfter(AstNode n, bool forceSpaces)
{
if (n == null) {
return;
}
TextLocation location = n.EndLocation;
int offset = document.GetOffset(location);
if (location.Column > document.GetLineByNumber(location.Line).Length) {
return;
}
int i = offset;
while (i < document.TextLength && IsSpacing (document.GetCharAt (i))) {
i++;
}
ForceSpace(offset - 1, i, forceSpaces);
}
int ForceSpacesBefore(AstNode n, bool forceSpaces)
{
if (n == null || n.IsNull) {
return 0;
}
TextLocation location = n.StartLocation;
// respect manual line breaks.
if (location.Column <= 1 || GetIndentation(location.Line).Length == location.Column - 1) {
return 0;
}
int offset = document.GetOffset(location);
int i = offset - 1;
while (i >= 0 && IsSpacing (document.GetCharAt (i))) {
i--;
}
ForceSpace(i, offset, forceSpaces);
return i;
}
int ForceSpacesBeforeRemoveNewLines(AstNode n, bool forceSpace = true)
{
if (n == null || n.IsNull) {
return 0;
}
int offset = document.GetOffset(n.StartLocation);
int i = offset - 1;
while (i >= 0) {
char ch = document.GetCharAt(i);
if (!IsSpacing(ch) && ch != '\r' && ch != '\n')
break;
i--;
}
var length = Math.Max(0, (offset - 1) - i);
AddChange(i + 1, length, forceSpace ? " " : "");
return i;
}
static bool NoWhitespacePredicate(AstNode arg)
{
return !(arg is NewLineNode || arg is WhitespaceNode);
}
static bool IsMember(AstNode nextSibling)
{
return nextSibling != null && nextSibling.NodeType == NodeType.Member;
}
void FormatParameters(AstNode node)
{
Wrapping methodCallArgumentWrapping;
bool newLineAferMethodCallOpenParentheses;
bool methodClosingParenthesesOnNewLine;
bool spaceWithinMethodCallParentheses;
bool spaceAfterMethodCallParameterComma;
bool spaceBeforeMethodCallParameterComma;
CSharpTokenNode rParToken;
AstNodeCollection<ParameterDeclaration> parameters;
var constructorDeclaration = node as ConstructorDeclaration;
if (constructorDeclaration != null) {
methodCallArgumentWrapping = policy.MethodDeclarationParameterWrapping;
newLineAferMethodCallOpenParentheses = policy.NewLineAferMethodDeclarationOpenParentheses;
methodClosingParenthesesOnNewLine = policy.MethodDeclarationClosingParenthesesOnNewLine;
spaceWithinMethodCallParentheses = policy.SpaceWithinConstructorDeclarationParentheses;
spaceAfterMethodCallParameterComma = policy.SpaceAfterConstructorDeclarationParameterComma;
spaceBeforeMethodCallParameterComma = policy.SpaceBeforeConstructorDeclarationParameterComma;
rParToken = constructorDeclaration.RParToken;
parameters = constructorDeclaration.Parameters;
} else if (node is IndexerDeclaration) {
var indexer = (IndexerDeclaration)node;
methodCallArgumentWrapping = policy.IndexerDeclarationParameterWrapping;
newLineAferMethodCallOpenParentheses = policy.NewLineAferIndexerDeclarationOpenBracket;
methodClosingParenthesesOnNewLine = policy.IndexerDeclarationClosingBracketOnNewLine;
spaceWithinMethodCallParentheses = policy.SpaceWithinIndexerDeclarationBracket;
spaceAfterMethodCallParameterComma = policy.SpaceAfterIndexerDeclarationParameterComma;
spaceBeforeMethodCallParameterComma = policy.SpaceBeforeIndexerDeclarationParameterComma;
rParToken = indexer.RBracketToken;
parameters = indexer.Parameters;
} else if (node is OperatorDeclaration) {
var op = (OperatorDeclaration)node;
methodCallArgumentWrapping = policy.MethodDeclarationParameterWrapping;
newLineAferMethodCallOpenParentheses = policy.NewLineAferMethodDeclarationOpenParentheses;
methodClosingParenthesesOnNewLine = policy.MethodDeclarationClosingParenthesesOnNewLine;
spaceWithinMethodCallParentheses = policy.SpaceWithinMethodDeclarationParentheses;
spaceAfterMethodCallParameterComma = policy.SpaceAfterMethodDeclarationParameterComma;
spaceBeforeMethodCallParameterComma = policy.SpaceBeforeMethodDeclarationParameterComma;
rParToken = op.RParToken;
parameters = op.Parameters;
} else {
var methodDeclaration = node as MethodDeclaration;
methodCallArgumentWrapping = policy.MethodDeclarationParameterWrapping;
newLineAferMethodCallOpenParentheses = policy.NewLineAferMethodDeclarationOpenParentheses;
methodClosingParenthesesOnNewLine = policy.MethodDeclarationClosingParenthesesOnNewLine;
spaceWithinMethodCallParentheses = policy.SpaceWithinMethodDeclarationParentheses;
spaceAfterMethodCallParameterComma = policy.SpaceAfterMethodDeclarationParameterComma;
spaceBeforeMethodCallParameterComma = policy.SpaceBeforeMethodDeclarationParameterComma;
rParToken = methodDeclaration.RParToken;
parameters = methodDeclaration.Parameters;
}
if (formatter.FormattingMode == FormattingMode.OnTheFly)
methodCallArgumentWrapping = Wrapping.DoNotChange;
bool wrapMethodCall = DoWrap(methodCallArgumentWrapping, rParToken, parameters.Count);
if (wrapMethodCall && parameters.Any()) {
if (newLineAferMethodCallOpenParentheses) {
curIndent.Push(IndentType.Continuation);
foreach (var arg in parameters) {
FixStatementIndentation(arg.StartLocation);
}
curIndent.Pop();
} else {
int extraSpaces = parameters.First().StartLocation.Column - 1 - curIndent.IndentString.Length;
curIndent.ExtraSpaces += extraSpaces;
foreach (var arg in parameters.Skip(1)) {
FixStatementIndentation(arg.StartLocation);
}
curIndent.ExtraSpaces -= extraSpaces;
}
if (!rParToken.IsNull) {
if (methodClosingParenthesesOnNewLine) {
FixStatementIndentation(rParToken.StartLocation);
} else {
ForceSpacesBeforeRemoveNewLines(rParToken, spaceWithinMethodCallParentheses);
}
}
} else {
foreach (var arg in parameters.Skip (1)) {
if (arg.GetPrevSibling(NoWhitespacePredicate) != null) {
if (methodCallArgumentWrapping == Wrapping.DoNotWrap) {
ForceSpacesBeforeRemoveNewLines(arg, spaceAfterMethodCallParameterComma && arg.GetPrevSibling(NoWhitespacePredicate).Role == Roles.Comma);
} else {
ForceSpacesBefore(arg, spaceAfterMethodCallParameterComma && arg.GetPrevSibling(NoWhitespacePredicate).Role == Roles.Comma);
}
}
arg.AcceptVisitor(this);
}
if (!rParToken.IsNull) {
if (methodCallArgumentWrapping == Wrapping.DoNotWrap) {
ForceSpacesBeforeRemoveNewLines(rParToken, spaceWithinMethodCallParentheses);
} else {
bool sameLine = rParToken.GetPrevNode().StartLocation.Line == rParToken.StartLocation.Line;
if (sameLine) {
ForceSpacesBeforeRemoveNewLines(rParToken, spaceWithinMethodCallParentheses);
} else {
FixStatementIndentation(rParToken.StartLocation);
}
}
}
}
if (!rParToken.IsNull) {
foreach (var comma in rParToken.Parent.Children.Where(n => n.Role == Roles.Comma)) {
ForceSpacesBefore(comma, spaceBeforeMethodCallParameterComma);
}
}
}
void ForceSpaceBefore(AstNode node, bool forceSpace)
{
_ForceSpaceBefore(document.GetOffset(node.StartLocation), forceSpace);
}
void _ForceSpaceBefore(int offset, bool forceSpace)
{
bool insertedSpace = false;
do {
char ch = document.GetCharAt(offset);
//Console.WriteLine (ch);
if (!IsSpacing(ch) && (insertedSpace || !forceSpace)) {
break;
}
if (ch == ' ' && forceSpace) {
if (insertedSpace) {
AddChange(offset, 1, null);
} else {
insertedSpace = true;
}
} else if (forceSpace) {
if (!insertedSpace) {
AddChange(offset, IsSpacing(ch) ? 1 : 0, " ");
insertedSpace = true;
} else if (IsSpacing(ch)) {
AddChange(offset, 1, null);
}
}
offset--;
} while (offset >= 0);
}
public void FixSemicolon(CSharpTokenNode semicolon)
{
if (semicolon.IsNull) {
return;
}
int endOffset = document.GetOffset(semicolon.StartLocation);
int offset = endOffset;
while (offset - 1 > 0 && char.IsWhiteSpace (document.GetCharAt (offset - 1))) {
offset--;
}
if (offset < endOffset) {
AddChange(offset, endOffset - offset, null);
}
}
void PlaceOnNewLine(NewLinePlacement newLine, AstNode keywordNode)
{
if (keywordNode == null || newLine == NewLinePlacement.DoNotCare) {
return;
}
var prev = keywordNode.GetPrevNode ();
if (prev is Comment || prev is PreProcessorDirective)
return;
int offset = document.GetOffset(keywordNode.StartLocation);
int whitespaceStart = SearchWhitespaceStart(offset);
string indentString = newLine == NewLinePlacement.NewLine ? options.EolMarker + curIndent.IndentString : " ";
AddChange(whitespaceStart, offset - whitespaceStart, indentString);
}
string nextStatementIndent;
void FixStatementIndentation(TextLocation location)
{
if (location.Line < 1 || location.Column < 1) {
Console.WriteLine("invalid location!");
return;
}
int offset = document.GetOffset(location);
if (offset <= 0) {
Console.WriteLine("possible wrong offset");
Console.WriteLine(Environment.StackTrace);
return;
}
bool isEmpty = IsLineIsEmptyUpToEol(offset);
int lineStart = SearchWhitespaceLineStart(offset);
string indentString = nextStatementIndent ?? (isEmpty ? "" : options.EolMarker) + curIndent.IndentString;
nextStatementIndent = null;
AddChange(lineStart, offset - lineStart, indentString);
}
void FixIndentation (AstNode node)
{
FixIndentation(node.StartLocation, 0);
}
void FixIndentation(TextLocation location, int relOffset)
{
if (location.Line < 1 || location.Line > document.LineCount) {
Console.WriteLine("Invalid location " + location);
Console.WriteLine(Environment.StackTrace);
return;
}
string lineIndent = GetIndentation(location.Line);
string indentString = curIndent.IndentString;
if (indentString != lineIndent && location.Column - 1 + relOffset == lineIndent.Length) {
AddChange(document.GetOffset(location.Line, 1), lineIndent.Length, indentString);
}
}
void FixIndentationForceNewLine(AstNode node)
{
if (node.GetPrevNode () is NewLineNode) {
FixIndentation(node);
} else {
int offset = document.GetOffset(node.StartLocation);
AddChange(offset, 0, curIndent.IndentString);
}
}
string GetIndentation(int lineNumber)
{
var line = document.GetLineByNumber(lineNumber);
var b = new StringBuilder ();
int endOffset = line.EndOffset;
for (int i = line.Offset; i < endOffset; i++) {
char c = document.GetCharAt(i);
if (!IsSpacing(c)) {
break;
}
b.Append(c);
}
return b.ToString();
}
void FixOpenBrace(BraceStyle braceStyle, AstNode lbrace)
{
if (lbrace.IsNull)
return;
switch (braceStyle) {
case BraceStyle.DoNotChange:
return;
case BraceStyle.BannerStyle:
case BraceStyle.EndOfLine:
var prev = lbrace.GetPrevNode();
while (prev is NewLineNode)
prev = prev.GetPrevNode();
if (prev is PreProcessorDirective)
return;
int prevOffset = document.GetOffset(prev.EndLocation);
if (prev is Comment || prev is PreProcessorDirective) {
int next = document.GetOffset(lbrace.GetNextNode ().StartLocation);
AddChange(prevOffset, next - prevOffset, "");
while (prev is Comment || prev is PreProcessorDirective)
prev = prev.GetPrevNode();
prevOffset = document.GetOffset(prev.EndLocation);
AddChange(prevOffset, 0, " {");
} else {
int braceOffset2 = document.GetOffset(lbrace.StartLocation);
AddChange(prevOffset, braceOffset2 - prevOffset, " ");
}
break;
case BraceStyle.EndOfLineWithoutSpace:
prev = lbrace.GetPrevNode();
while (prev is NewLineNode)
prev = prev.GetPrevNode();
if (prev is PreProcessorDirective)
return;
prevOffset = document.GetOffset(prev.EndLocation);
int braceOffset = document.GetOffset(lbrace.StartLocation);
AddChange(prevOffset, braceOffset - prevOffset, "");
break;
case BraceStyle.NextLine:
prev = lbrace.GetPrevNode();
while (prev is NewLineNode)
prev = prev.GetPrevNode();
if (prev is PreProcessorDirective)
return;
prevOffset = document.GetOffset(prev.EndLocation);
braceOffset = document.GetOffset(lbrace.StartLocation);
AddChange(prevOffset, braceOffset - prevOffset, options.EolMarker + curIndent.IndentString);
break;
case BraceStyle.NextLineShifted:
prev = lbrace.GetPrevNode();
while (prev is NewLineNode)
prev = prev.GetPrevNode();
if (prev is PreProcessorDirective)
return;
prevOffset = document.GetOffset(prev.EndLocation);
braceOffset = document.GetOffset(lbrace.StartLocation);
curIndent.Push(IndentType.Block);
AddChange(prevOffset, braceOffset - prevOffset, options.EolMarker + curIndent.IndentString);
curIndent.Pop();
break;
case BraceStyle.NextLineShifted2:
prev = lbrace.GetPrevNode();
while (prev is NewLineNode)
prev = prev.GetPrevNode();
if (prev is PreProcessorDirective)
return;
prevOffset = document.GetOffset(prev.EndLocation);
braceOffset = document.GetOffset(lbrace.StartLocation);
curIndent.Push(IndentType.Block);
AddChange(prevOffset, braceOffset - prevOffset, options.EolMarker + curIndent.IndentString);
curIndent.Pop();
break;
}
}
void CorrectClosingBrace (AstNode rbrace)
{
int braceOffset = document.GetOffset(rbrace.StartLocation);
var prevNode = rbrace.GetPrevNode();
int prevNodeOffset = prevNode != null ? document.GetOffset(prevNode.EndLocation) : 0;
if (prevNode is NewLineNode) {
AddChange(prevNodeOffset, braceOffset - prevNodeOffset, curIndent.IndentString);
} else {
AddChange(prevNodeOffset, braceOffset - prevNodeOffset, options.EolMarker + curIndent.IndentString);
}
}
void FixClosingBrace(BraceStyle braceStyle, AstNode rbrace)
{
if (rbrace.IsNull)
return;
switch (braceStyle) {
case BraceStyle.DoNotChange:
return;
case BraceStyle.NextLineShifted:
case BraceStyle.BannerStyle:
curIndent.Push(IndentType.Block);
CorrectClosingBrace (rbrace);
curIndent.Pop ();
break;
case BraceStyle.EndOfLineWithoutSpace:
case BraceStyle.EndOfLine:
case BraceStyle.NextLine:
CorrectClosingBrace (rbrace);
break;
case BraceStyle.NextLineShifted2:
curIndent.Push(IndentType.Block);
CorrectClosingBrace (rbrace);
curIndent.Pop ();
break;
}
}
}
}