From 8b58930f12a923ab33576a0610dc5554933565f8 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Wed, 25 Dec 2013 19:27:02 +0100 Subject: [PATCH] Merge NRefactory changes from the SharpDevelop repository. Includes Siegfried's work on the output visitor (ITokenWriter) and some minor bugfixes. --- .../CodeActions/UseStringFormatAction.cs | 2 +- ICSharpCode.NRefactory.CSharp/Ast/AstNode.cs | 16 + .../Ast/CSharpModifierToken.cs | 45 ++ .../Expressions/NullReferenceExpression.cs | 10 +- .../Ast/Expressions/PrimitiveExpression.cs | 47 +- .../Ast/Identifier.cs | 6 + .../Ast/PrimitiveType.cs | 7 + .../ICSharpCode.NRefactory.CSharp.csproj | 5 +- .../OutputVisitor/CSharpAmbience.cs | 153 +++-- .../OutputVisitor/CSharpOutputVisitor.cs | 629 +++++------------- .../OutputVisitor/IOutputFormatter.cs | 60 -- .../OutputVisitor/ITokenWriter.cs | 161 +++++ .../InsertMissingTokensDecorator.cs | 122 ++++ .../InsertRequiredSpacesDecorator.cs | 184 +++++ .../OutputVisitor/InsertSpecialsDecorator.cs | 157 +++++ .../TextWriterOutputFormatter.cs | 359 +++++++--- .../Refactoring/Script.cs | 14 +- .../TypeSystem/CSharpUnresolvedFile.cs | 2 +- .../Xml/XmlReaderTest.cs | 4 +- .../CSharp/CSharpOutputVisitorTests.cs | 2 +- .../InsertMissingTokensDecoratorTests.cs | 87 +++ .../CSharp/InsertParenthesesVisitorTests.cs | 4 +- .../CSharp/Parser/ConsistencyChecker.cs | 101 +++ .../CSharp/Parser/ParseSelfTests.cs | 84 +-- .../ICSharpCode.NRefactory.Tests.csproj | 3 + .../DocumentationElement.cs | 9 +- 26 files changed, 1482 insertions(+), 791 deletions(-) delete mode 100644 ICSharpCode.NRefactory.CSharp/OutputVisitor/IOutputFormatter.cs create mode 100644 ICSharpCode.NRefactory.CSharp/OutputVisitor/ITokenWriter.cs create mode 100644 ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertMissingTokensDecorator.cs create mode 100644 ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertRequiredSpacesDecorator.cs create mode 100644 ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertSpecialsDecorator.cs create mode 100644 ICSharpCode.NRefactory.Tests/CSharp/InsertMissingTokensDecoratorTests.cs create mode 100644 ICSharpCode.NRefactory.Tests/CSharp/Parser/ConsistencyChecker.cs diff --git a/ICSharpCode.NRefactory.CSharp.Refactoring/CodeActions/UseStringFormatAction.cs b/ICSharpCode.NRefactory.CSharp.Refactoring/CodeActions/UseStringFormatAction.cs index 51112a012..2a299bb78 100644 --- a/ICSharpCode.NRefactory.CSharp.Refactoring/CodeActions/UseStringFormatAction.cs +++ b/ICSharpCode.NRefactory.CSharp.Refactoring/CodeActions/UseStringFormatAction.cs @@ -114,7 +114,7 @@ public override IEnumerable GetActions(RefactoringContext context) format.Append('"'); if (verbatim) format.Insert(0, '@'); - formatLiteral.LiteralValue = format.ToString(); + formatLiteral.SetValue(format.ToString(), format.ToString()); if (arguments.Count > 0) script.Replace(expr, formatInvocation); else diff --git a/ICSharpCode.NRefactory.CSharp/Ast/AstNode.cs b/ICSharpCode.NRefactory.CSharp/Ast/AstNode.cs index eb89e5916..d5294f99b 100644 --- a/ICSharpCode.NRefactory.CSharp/Ast/AstNode.cs +++ b/ICSharpCode.NRefactory.CSharp/Ast/AstNode.cs @@ -411,6 +411,8 @@ public void AddChild (T child, Role role) where T : AstNode if (child == null || child.IsNull) return; ThrowIfFrozen(); + if (child == this) + throw new ArgumentException ("Cannot add a node to itself as a child.", "child"); if (child.parent != null) throw new ArgumentException ("Node is already used in another tree.", "child"); if (child.IsFrozen) @@ -418,6 +420,20 @@ public void AddChild (T child, Role role) where T : AstNode AddChildUnsafe (child, role); } + public void AddChildWithExistingRole (AstNode child) + { + if (child == null || child.IsNull) + return; + ThrowIfFrozen(); + if (child == this) + throw new ArgumentException ("Cannot add a node to itself as a child.", "child"); + if (child.parent != null) + throw new ArgumentException ("Node is already used in another tree.", "child"); + if (child.IsFrozen) + throw new ArgumentException ("Cannot add a frozen node.", "child"); + AddChildUnsafe (child, child.Role); + } + /// /// Adds a child without performing any safety checks. /// diff --git a/ICSharpCode.NRefactory.CSharp/Ast/CSharpModifierToken.cs b/ICSharpCode.NRefactory.CSharp/Ast/CSharpModifierToken.cs index ddcd3ff3a..1a46006f2 100644 --- a/ICSharpCode.NRefactory.CSharp/Ast/CSharpModifierToken.cs +++ b/ICSharpCode.NRefactory.CSharp/Ast/CSharpModifierToken.cs @@ -169,5 +169,50 @@ public static int GetModifierLength(Modifiers modifier) throw new NotSupportedException("Invalid value for Modifiers"); } } + + public static Modifiers GetModifierValue(string modifier) + { + switch (modifier) { + case "private": + return Modifiers.Private; + case "internal": + return Modifiers.Internal; + case "protected": + return Modifiers.Protected; + case "public": + return Modifiers.Public; + case "abstract": + return Modifiers.Abstract; + case "virtual": + return Modifiers.Virtual; + case "sealed": + return Modifiers.Sealed; + case "static": + return Modifiers.Static; + case "override": + return Modifiers.Override; + case "readonly": + return Modifiers.Readonly; + case "const": + return Modifiers.Const; + case "new": + return Modifiers.New; + case "partial": + return Modifiers.Partial; + case "extern": + return Modifiers.Extern; + case "volatile": + return Modifiers.Volatile; + case "unsafe": + return Modifiers.Unsafe; + case "async": + return Modifiers.Async; + case "any": + // even though it's used for pattern matching only, 'any' needs to be in this list to be usable in the AST + return Modifiers.Any; + default: + throw new NotSupportedException("Invalid value for Modifiers"); + } + } } } \ No newline at end of file diff --git a/ICSharpCode.NRefactory.CSharp/Ast/Expressions/NullReferenceExpression.cs b/ICSharpCode.NRefactory.CSharp/Ast/Expressions/NullReferenceExpression.cs index a7a3ab7b5..fbfeb6f91 100644 --- a/ICSharpCode.NRefactory.CSharp/Ast/Expressions/NullReferenceExpression.cs +++ b/ICSharpCode.NRefactory.CSharp/Ast/Expressions/NullReferenceExpression.cs @@ -1,6 +1,6 @@ // // NullReferenceExpression.cs -// +// // Author: // Mike Krüger // @@ -38,6 +38,12 @@ public override TextLocation StartLocation { } } + internal void SetStartLocation(TextLocation value) + { + ThrowIfFrozen(); + this.location = value; + } + public override TextLocation EndLocation { get { return new TextLocation (location.Line, location.Column + "null".Length); @@ -57,7 +63,7 @@ public override void AcceptVisitor (IAstVisitor visitor) { visitor.VisitNullReferenceExpression (this); } - + public override T AcceptVisitor (IAstVisitor visitor) { return visitor.VisitNullReferenceExpression (this); diff --git a/ICSharpCode.NRefactory.CSharp/Ast/Expressions/PrimitiveExpression.cs b/ICSharpCode.NRefactory.CSharp/Ast/Expressions/PrimitiveExpression.cs index e9e9ea620..adfe7c3ba 100644 --- a/ICSharpCode.NRefactory.CSharp/Ast/Expressions/PrimitiveExpression.cs +++ b/ICSharpCode.NRefactory.CSharp/Ast/Expressions/PrimitiveExpression.cs @@ -1,6 +1,6 @@ // // PrimitiveExpression.cs -// +// // Author: // Mike Krüger // @@ -42,13 +42,20 @@ public override TextLocation StartLocation { } } + internal void SetStartLocation(TextLocation value) + { + ThrowIfFrozen(); + this.startLocation = value; + this.endLocation = null; + } + string literalValue; TextLocation? endLocation; public override TextLocation EndLocation { get { if (!endLocation.HasValue) { - endLocation = value is string ? AdvanceLocation (StartLocation, literalValue) : - new TextLocation (StartLocation.Line, StartLocation.Column + literalValue.Length); + endLocation = value is string ? AdvanceLocation (StartLocation, literalValue ?? "") : + new TextLocation (StartLocation.Line, StartLocation.Column + (literalValue ?? "").Length); } return endLocation.Value; } @@ -58,46 +65,56 @@ public override TextLocation EndLocation { public object Value { get { return this.value; } - set { - ThrowIfFrozen(); + set { + ThrowIfFrozen(); this.value = value; + literalValue = null; } } + /// Never returns null. public string LiteralValue { + get { return literalValue ?? ""; } + } + + /// Can be null. + public string UnsafeLiteralValue { get { return literalValue; } - set { - if (value == null) - throw new ArgumentNullException(); - ThrowIfFrozen(); - literalValue = value; - } + } + + public void SetValue(object value, string literalValue) + { + if (value == null) + throw new ArgumentNullException(); + ThrowIfFrozen(); + this.value = value; + this.literalValue = literalValue; } public PrimitiveExpression (object value) { this.Value = value; - this.literalValue = ""; + this.literalValue = null; } public PrimitiveExpression (object value, string literalValue) { this.Value = value; - this.literalValue = literalValue ?? ""; + this.literalValue = literalValue; } public PrimitiveExpression (object value, TextLocation startLocation, string literalValue) { this.Value = value; this.startLocation = startLocation; - this.literalValue = literalValue ?? ""; + this.literalValue = literalValue; } public override void AcceptVisitor (IAstVisitor visitor) { visitor.VisitPrimitiveExpression (this); } - + public override T AcceptVisitor (IAstVisitor visitor) { return visitor.VisitPrimitiveExpression (this); diff --git a/ICSharpCode.NRefactory.CSharp/Ast/Identifier.cs b/ICSharpCode.NRefactory.CSharp/Ast/Identifier.cs index fad153b03..3244e746e 100644 --- a/ICSharpCode.NRefactory.CSharp/Ast/Identifier.cs +++ b/ICSharpCode.NRefactory.CSharp/Ast/Identifier.cs @@ -83,6 +83,12 @@ public override TextLocation StartLocation { } } + internal void SetStartLocation(TextLocation value) + { + ThrowIfFrozen(); + this.startLocation = value; + } + const uint verbatimBit = 1u << AstNodeFlagsUsedBits; public bool IsVerbatim { diff --git a/ICSharpCode.NRefactory.CSharp/Ast/PrimitiveType.cs b/ICSharpCode.NRefactory.CSharp/Ast/PrimitiveType.cs index 8861e32ee..5b52a37ac 100644 --- a/ICSharpCode.NRefactory.CSharp/Ast/PrimitiveType.cs +++ b/ICSharpCode.NRefactory.CSharp/Ast/PrimitiveType.cs @@ -71,6 +71,13 @@ public override TextLocation StartLocation { return location; } } + + internal void SetStartLocation(TextLocation value) + { + ThrowIfFrozen(); + this.location = value; + } + public override TextLocation EndLocation { get { return new TextLocation (location.Line, location.Column + keyword.Length); diff --git a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj index eab91fc31..9b138abb2 100644 --- a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj +++ b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj @@ -209,9 +209,12 @@ + - + + + diff --git a/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpAmbience.cs b/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpAmbience.cs index 7a94b2e40..84d72ff75 100644 --- a/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpAmbience.cs +++ b/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpAmbience.cs @@ -37,98 +37,98 @@ public string ConvertEntity(IEntity entity) throw new ArgumentNullException("entity"); StringWriter writer = new StringWriter(); - ConvertEntity(entity, new TextWriterOutputFormatter(writer), FormattingOptionsFactory.CreateMono ()); + ConvertEntity(entity, new TextWriterTokenWriter(writer), FormattingOptionsFactory.CreateMono ()); return writer.ToString(); } - public void ConvertEntity(IEntity entity, IOutputFormatter formatter, CSharpFormattingOptions formattingPolicy) + public void ConvertEntity(IEntity entity, TokenWriter writer, CSharpFormattingOptions formattingPolicy) { if (entity == null) throw new ArgumentNullException("entity"); - if (formatter == null) - throw new ArgumentNullException("formatter"); + if (writer == null) + throw new ArgumentNullException("writer"); if (formattingPolicy == null) throw new ArgumentNullException("options"); TypeSystemAstBuilder astBuilder = CreateAstBuilder(); EntityDeclaration node = astBuilder.ConvertEntity(entity); - PrintModifiers(node.Modifiers, formatter); + PrintModifiers(node.Modifiers, writer); if ((ConversionFlags & ConversionFlags.ShowDefinitionKeyword) == ConversionFlags.ShowDefinitionKeyword) { if (node is TypeDeclaration) { switch (((TypeDeclaration)node).ClassType) { case ClassType.Class: - formatter.WriteKeyword("class"); + writer.WriteKeyword(Roles.ClassKeyword, "class"); break; case ClassType.Struct: - formatter.WriteKeyword("struct"); + writer.WriteKeyword(Roles.StructKeyword, "struct"); break; case ClassType.Interface: - formatter.WriteKeyword("interface"); + writer.WriteKeyword(Roles.InterfaceKeyword, "interface"); break; case ClassType.Enum: - formatter.WriteKeyword("enum"); + writer.WriteKeyword(Roles.EnumKeyword, "enum"); break; default: throw new Exception("Invalid value for ClassType"); } - formatter.Space(); + writer.Space(); } else if (node is DelegateDeclaration) { - formatter.WriteKeyword("delegate"); - formatter.Space(); + writer.WriteKeyword(Roles.DelegateKeyword, "delegate"); + writer.Space(); } else if (node is EventDeclaration) { - formatter.WriteKeyword("event"); - formatter.Space(); + writer.WriteKeyword(EventDeclaration.EventKeywordRole, "event"); + writer.Space(); } } if ((ConversionFlags & ConversionFlags.ShowReturnType) == ConversionFlags.ShowReturnType) { var rt = node.GetChildByRole(Roles.Type); if (!rt.IsNull) { - rt.AcceptVisitor(new CSharpOutputVisitor(formatter, formattingPolicy)); - formatter.Space(); + rt.AcceptVisitor(new CSharpOutputVisitor(writer, formattingPolicy)); + writer.Space(); } } if (entity is ITypeDefinition) - WriteTypeDeclarationName((ITypeDefinition)entity, formatter, formattingPolicy); + WriteTypeDeclarationName((ITypeDefinition)entity, writer, formattingPolicy); else - WriteMemberDeclarationName((IMember)entity, formatter, formattingPolicy); + WriteMemberDeclarationName((IMember)entity, writer, formattingPolicy); if ((ConversionFlags & ConversionFlags.ShowParameterList) == ConversionFlags.ShowParameterList && HasParameters(entity)) { - formatter.WriteToken(entity.SymbolKind == SymbolKind.Indexer ? "[" : "("); + writer.WriteToken(entity.SymbolKind == SymbolKind.Indexer ? Roles.LBracket : Roles.LPar, entity.SymbolKind == SymbolKind.Indexer ? "[" : "("); bool first = true; foreach (var param in node.GetChildrenByRole(Roles.Parameter)) { if (first) { first = false; } else { - formatter.WriteToken(","); - formatter.Space(); + writer.WriteToken(Roles.Comma, ","); + writer.Space(); } - param.AcceptVisitor(new CSharpOutputVisitor(formatter, formattingPolicy)); + param.AcceptVisitor(new CSharpOutputVisitor(writer, formattingPolicy)); } - formatter.WriteToken(entity.SymbolKind == SymbolKind.Indexer ? "]" : ")"); + writer.WriteToken(entity.SymbolKind == SymbolKind.Indexer ? Roles.RBracket : Roles.RPar, entity.SymbolKind == SymbolKind.Indexer ? "]" : ")"); } if ((ConversionFlags & ConversionFlags.ShowBody) == ConversionFlags.ShowBody && !(node is TypeDeclaration)) { IProperty property = entity as IProperty; if (property != null) { - formatter.Space(); - formatter.WriteToken("{"); - formatter.Space(); + writer.Space(); + writer.WriteToken(Roles.LBrace, "{"); + writer.Space(); if (property.CanGet) { - formatter.WriteKeyword("get"); - formatter.WriteToken(";"); - formatter.Space(); + writer.WriteKeyword(PropertyDeclaration.GetKeywordRole, "get"); + writer.WriteToken(Roles.Semicolon, ";"); + writer.Space(); } if (property.CanSet) { - formatter.WriteKeyword("set"); - formatter.WriteToken(";"); - formatter.Space(); + writer.WriteKeyword(PropertyDeclaration.SetKeywordRole, "set"); + writer.WriteToken(Roles.Semicolon, ";"); + writer.Space(); } - formatter.WriteToken("}"); + writer.WriteToken(Roles.RBrace, "}"); } else { - formatter.WriteToken(";"); + writer.WriteToken(Roles.Semicolon, ";"); } } } @@ -160,87 +160,96 @@ TypeSystemAstBuilder CreateAstBuilder() return astBuilder; } - void WriteTypeDeclarationName(ITypeDefinition typeDef, IOutputFormatter formatter, CSharpFormattingOptions formattingPolicy) + void WriteTypeDeclarationName(ITypeDefinition typeDef, TokenWriter writer, CSharpFormattingOptions formattingPolicy) { TypeSystemAstBuilder astBuilder = CreateAstBuilder(); + EntityDeclaration node = astBuilder.ConvertEntity(typeDef); if (typeDef.DeclaringTypeDefinition != null) { - WriteTypeDeclarationName(typeDef.DeclaringTypeDefinition, formatter, formattingPolicy); - formatter.WriteToken("."); + WriteTypeDeclarationName(typeDef.DeclaringTypeDefinition, writer, formattingPolicy); + writer.WriteToken(Roles.Dot, "."); } else if ((ConversionFlags & ConversionFlags.UseFullyQualifiedTypeNames) == ConversionFlags.UseFullyQualifiedTypeNames) { - formatter.WriteIdentifier(typeDef.Namespace); - formatter.WriteToken("."); + WriteQualifiedName(typeDef.Namespace, writer, formattingPolicy); + writer.WriteToken(Roles.Dot, "."); } - formatter.WriteIdentifier(typeDef.Name); + writer.WriteIdentifier(node.NameToken); if ((ConversionFlags & ConversionFlags.ShowTypeParameterList) == ConversionFlags.ShowTypeParameterList) { - var outputVisitor = new CSharpOutputVisitor(formatter, formattingPolicy); - outputVisitor.WriteTypeParameters(astBuilder.ConvertEntity(typeDef).GetChildrenByRole(Roles.TypeParameter)); + var outputVisitor = new CSharpOutputVisitor(writer, formattingPolicy); + outputVisitor.WriteTypeParameters(node.GetChildrenByRole(Roles.TypeParameter)); } } - void WriteMemberDeclarationName(IMember member, IOutputFormatter formatter, CSharpFormattingOptions formattingPolicy) + void WriteMemberDeclarationName(IMember member, TokenWriter writer, CSharpFormattingOptions formattingPolicy) { TypeSystemAstBuilder astBuilder = CreateAstBuilder(); + EntityDeclaration node = astBuilder.ConvertEntity(member); if ((ConversionFlags & ConversionFlags.ShowDeclaringType) == ConversionFlags.ShowDeclaringType) { - ConvertType(member.DeclaringType, formatter, formattingPolicy); - formatter.WriteToken("."); + ConvertType(member.DeclaringType, writer, formattingPolicy); + writer.WriteToken(Roles.Dot, "."); } switch (member.SymbolKind) { case SymbolKind.Indexer: - formatter.WriteKeyword("this"); + writer.WriteKeyword(Roles.Identifier, "this"); break; case SymbolKind.Constructor: - formatter.WriteIdentifier(member.DeclaringType.Name); + WriteQualifiedName(member.DeclaringType.Name, writer, formattingPolicy); break; case SymbolKind.Destructor: - formatter.WriteToken("~"); - formatter.WriteIdentifier(member.DeclaringType.Name); + writer.WriteToken(DestructorDeclaration.TildeRole, "~"); + WriteQualifiedName(member.DeclaringType.Name, writer, formattingPolicy); break; case SymbolKind.Operator: switch (member.Name) { case "op_Implicit": - formatter.WriteKeyword("implicit"); - formatter.Space(); - formatter.WriteKeyword("operator"); - formatter.Space(); - ConvertType(member.ReturnType, formatter, formattingPolicy); + writer.WriteKeyword(OperatorDeclaration.ImplicitRole, "implicit"); + writer.Space(); + writer.WriteKeyword(OperatorDeclaration.OperatorKeywordRole, "operator"); + writer.Space(); + ConvertType(member.ReturnType, writer, formattingPolicy); break; case "op_Explicit": - formatter.WriteKeyword("explicit"); - formatter.Space(); - formatter.WriteKeyword("operator"); - formatter.Space(); - ConvertType(member.ReturnType, formatter, formattingPolicy); + writer.WriteKeyword(OperatorDeclaration.ExplicitRole, "explicit"); + writer.Space(); + writer.WriteKeyword(OperatorDeclaration.OperatorKeywordRole, "operator"); + writer.Space(); + ConvertType(member.ReturnType, writer, formattingPolicy); break; default: - formatter.WriteKeyword("operator"); - formatter.Space(); + writer.WriteKeyword(OperatorDeclaration.OperatorKeywordRole, "operator"); + writer.Space(); var operatorType = OperatorDeclaration.GetOperatorType(member.Name); if (operatorType.HasValue) - formatter.WriteToken(OperatorDeclaration.GetToken(operatorType.Value)); + writer.WriteToken(OperatorDeclaration.GetRole(operatorType.Value), OperatorDeclaration.GetToken(operatorType.Value)); else - formatter.WriteIdentifier(member.Name); + writer.WriteIdentifier(node.NameToken); break; } break; default: - formatter.WriteIdentifier(member.Name); + writer.WriteIdentifier(Identifier.Create(member.Name)); break; } if ((ConversionFlags & ConversionFlags.ShowTypeParameterList) == ConversionFlags.ShowTypeParameterList && member.SymbolKind == SymbolKind.Method) { - var outputVisitor = new CSharpOutputVisitor(formatter, formattingPolicy); - outputVisitor.WriteTypeParameters(astBuilder.ConvertEntity(member).GetChildrenByRole(Roles.TypeParameter)); + var outputVisitor = new CSharpOutputVisitor(writer, formattingPolicy); + outputVisitor.WriteTypeParameters(node.GetChildrenByRole(Roles.TypeParameter)); } } - void PrintModifiers(Modifiers modifiers, IOutputFormatter formatter) + void PrintModifiers(Modifiers modifiers, TokenWriter writer) { foreach (var m in CSharpModifierToken.AllModifiers) { if ((modifiers & m) == m) { - formatter.WriteKeyword(CSharpModifierToken.GetModifierName(m)); - formatter.Space(); + writer.WriteKeyword(EntityDeclaration.ModifierRole, CSharpModifierToken.GetModifierName(m)); + writer.Space(); } } } + + void WriteQualifiedName(string name, TokenWriter writer, CSharpFormattingOptions formattingPolicy) + { + var node = AstType.Create(name); + var outputVisitor = new CSharpOutputVisitor(writer, formattingPolicy); + node.AcceptVisitor(outputVisitor); + } #endregion public string ConvertVariable(IVariable v) @@ -260,16 +269,16 @@ public string ConvertType(IType type) return astType.ToString(); } - public void ConvertType(IType type, IOutputFormatter formatter, CSharpFormattingOptions formattingPolicy) + public void ConvertType(IType type, TokenWriter writer, CSharpFormattingOptions formattingPolicy) { TypeSystemAstBuilder astBuilder = CreateAstBuilder(); AstType astType = astBuilder.ConvertType(type); - astType.AcceptVisitor(new CSharpOutputVisitor(formatter, formattingPolicy)); + astType.AcceptVisitor(new CSharpOutputVisitor(writer, formattingPolicy)); } public string ConvertConstantValue(object constantValue) { - return CSharpOutputVisitor.PrintPrimitiveValue(constantValue); + return TextWriterTokenWriter.PrintPrimitiveValue(constantValue); } public string WrapComment(string comment) diff --git a/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpOutputVisitor.cs index 927082dc8..fcbf50eb2 100644 --- a/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -25,6 +25,7 @@ using System.Threading.Tasks; using ICSharpCode.NRefactory.PatternMatching; using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.CSharp; namespace ICSharpCode.NRefactory.CSharp { @@ -33,27 +34,9 @@ namespace ICSharpCode.NRefactory.CSharp /// public class CSharpOutputVisitor : IAstVisitor { - readonly IOutputFormatter formatter; + readonly TokenWriter writer; readonly CSharpFormattingOptions policy; readonly Stack containerStack = new Stack (); - readonly Stack positionStack = new Stack (); - - /// - /// Used to insert the minimal amount of spaces so that the lexer recognizes the tokens that were written. - /// - LastWritten lastWritten; - - enum LastWritten - { - Whitespace, - Other, - KeywordOrIdentifier, - Plus, - Minus, - Ampersand, - QuestionMark, - Division - } public CSharpOutputVisitor (TextWriter textWriter, CSharpFormattingOptions formattingPolicy) { @@ -63,19 +46,19 @@ public CSharpOutputVisitor (TextWriter textWriter, CSharpFormattingOptions forma if (formattingPolicy == null) { throw new ArgumentNullException ("formattingPolicy"); } - this.formatter = new TextWriterOutputFormatter (textWriter); + this.writer = TokenWriter.Create(textWriter); this.policy = formattingPolicy; } - public CSharpOutputVisitor (IOutputFormatter formatter, CSharpFormattingOptions formattingPolicy) + public CSharpOutputVisitor (TokenWriter writer, CSharpFormattingOptions formattingPolicy) { - if (formatter == null) { - throw new ArgumentNullException ("formatter"); + if (writer == null) { + throw new ArgumentNullException ("writer"); } if (formattingPolicy == null) { throw new ArgumentNullException ("formattingPolicy"); } - this.formatter = formatter; + this.writer = new InsertSpecialsDecorator(new InsertRequiredSpacesDecorator(writer)); this.policy = formattingPolicy; } @@ -85,84 +68,15 @@ void StartNode(AstNode node) // Ensure that nodes are visited in the proper nested order. // Jumps to different subtrees are allowed only for the child of a placeholder node. Debug.Assert(containerStack.Count == 0 || node.Parent == containerStack.Peek() || containerStack.Peek().NodeType == NodeType.Pattern); - if (positionStack.Count > 0) { - WriteSpecialsUpToNode(node); - } containerStack.Push(node); - positionStack.Push(node.FirstChild); - formatter.StartNode(node); + writer.StartNode(node); } void EndNode(AstNode node) { Debug.Assert(node == containerStack.Peek()); - AstNode pos = positionStack.Pop(); - Debug.Assert(pos == null || pos.Parent == node); - WriteSpecials(pos, null); containerStack.Pop(); - formatter.EndNode(node); - } - #endregion - - #region WriteSpecials - /// - /// Writes all specials from start to end (exclusive). Does not touch the positionStack. - /// - void WriteSpecials(AstNode start, AstNode end) - { - for (AstNode pos = start; pos != end; pos = pos.NextSibling) { - if (pos.Role == Roles.Comment || pos.Role == Roles.NewLine || pos.Role == Roles.PreProcessorDirective) { - pos.AcceptVisitor(this); - } - } - } - - /// - /// Writes all specials between the current position (in the positionStack) and the next - /// node with the specified role. Advances the current position. - /// - void WriteSpecialsUpToRole(Role role) - { - WriteSpecialsUpToRole(role, null); - } - - void WriteSpecialsUpToRole(Role role, AstNode nextNode) - { - if (positionStack.Count == 0) { - return; - } - // Look for the role between the current position and the nextNode. - for (AstNode pos = positionStack.Peek(); pos != null && pos != nextNode; pos = pos.NextSibling) { - if (pos.Role == role) { - WriteSpecials(positionStack.Pop(), pos); - // Push the next sibling because the node matching the role is not a special, - // and should be considered to be already handled. - positionStack.Push(pos.NextSibling); - // This is necessary for OptionalComma() to work correctly. - break; - } - } - } - - /// - /// Writes all specials between the current position (in the positionStack) and the specified node. - /// Advances the current position. - /// - void WriteSpecialsUpToNode(AstNode node) - { - if (positionStack.Count == 0) { - return; - } - for (AstNode pos = positionStack.Peek(); pos != null; pos = pos.NextSibling) { - if (pos == node) { - WriteSpecials(positionStack.Pop(), pos); - // Push the next sibling because the node itself is not a special, - // and should be considered to be already handled. - positionStack.Push(pos.NextSibling); - // This is necessary for OptionalComma() to work correctly. - break; - } - } + writer.EndNode(node); } #endregion @@ -174,11 +88,9 @@ void WriteSpecialsUpToNode(AstNode node) /// When set prevents printing a space after comma. void Comma(AstNode nextNode, bool noSpaceAfterComma = false) { - WriteSpecialsUpToRole(Roles.Comma, nextNode); Space(policy.SpaceBeforeBracketComma); // TODO: Comma policy has changed. - formatter.WriteToken(","); - lastWritten = LastWritten.Other; + writer.WriteToken(Roles.Comma, ","); Space(!noSpaceAfterComma && policy.SpaceAfterBracketComma); // TODO: Comma policy has changed. } @@ -186,10 +98,9 @@ void Comma(AstNode nextNode, bool noSpaceAfterComma = false) /// /// Writes an optional comma, e.g. at the end of an enum declaration or in an array initializer /// - void OptionalComma() + void OptionalComma(AstNode pos) { // Look if there's a comma after the current node, and insert it if it exists. - AstNode pos = positionStack.Peek(); while (pos != null && pos.NodeType == NodeType.Whitespace) { pos = pos.NextSibling; } @@ -201,12 +112,11 @@ void OptionalComma() /// /// Writes an optional semicolon, e.g. at the end of a type or namespace declaration. /// - void OptionalSemicolon() + void OptionalSemicolon(AstNode pos) { // Look if there's a semicolon after the current node, and insert it if it exists. - AstNode pos = positionStack.Peek(); while (pos != null && pos.NodeType == NodeType.Whitespace) { - pos = pos.NextSibling; + pos = pos.PrevSibling; } if (pos != null && pos.Role == Roles.Semicolon) { Semicolon(); @@ -284,6 +194,8 @@ void WriteCommaSeparatedListInBrackets(IEnumerable list) #endregion #region Write tokens + bool isAtStartOfLine = true; + /// /// Writes a keyword, and all specials up to /// @@ -294,40 +206,20 @@ void WriteKeyword(TokenRole tokenRole) void WriteKeyword(string token, Role tokenRole = null) { - if (tokenRole != null) { - WriteSpecialsUpToRole(tokenRole); - } - if (lastWritten == LastWritten.KeywordOrIdentifier) { - formatter.Space(); - } - formatter.WriteKeyword(token); - lastWritten = LastWritten.KeywordOrIdentifier; + writer.WriteKeyword(tokenRole, token); + isAtStartOfLine = false; } -/* void WriteKeyword (string keyword, Role tokenRole) + void WriteIdentifier(Identifier identifier) { - WriteSpecialsUpToRole (tokenRole); - if (lastWritten == LastWritten.KeywordOrIdentifier) - formatter.Space (); - formatter.WriteKeyword (keyword); - lastWritten = LastWritten.KeywordOrIdentifier; - }*/ + writer.WriteIdentifier(identifier); + isAtStartOfLine = false; + } - void WriteIdentifier(string identifier, Role identifierRole = null) + void WriteIdentifier(string identifier) { - WriteSpecialsUpToRole(identifierRole ?? Roles.Identifier); - if (IsKeyword(identifier, containerStack.Peek())) { - if (lastWritten == LastWritten.KeywordOrIdentifier) { - Space(); - } - // this space is not strictly required, so we call Space() - formatter.WriteToken("@"); - } else if (lastWritten == LastWritten.KeywordOrIdentifier) { - formatter.Space(); - // this space is strictly required, so we directly call the formatter - } - formatter.WriteIdentifier(identifier); - lastWritten = LastWritten.KeywordOrIdentifier; + AstType.Create(identifier).AcceptVisitor(this); + isAtStartOfLine = false; } void WriteToken(TokenRole tokenRole) @@ -337,34 +229,8 @@ void WriteToken(TokenRole tokenRole) void WriteToken(string token, Role tokenRole) { - WriteSpecialsUpToRole(tokenRole); - // Avoid that two +, - or ? tokens are combined into a ++, -- or ?? token. - // Note that we don't need to handle tokens like = because there's no valid - // C# program that contains the single token twice in a row. - // (for +, - and &, this can happen with unary operators; - // for ?, this can happen in "a is int? ? b : c" or "a as int? ?? 0"; - // and for /, this can happen with "1/ *ptr" or "1/ //comment".) - if (lastWritten == LastWritten.Plus && token [0] == '+' - || lastWritten == LastWritten.Minus && token [0] == '-' - || lastWritten == LastWritten.Ampersand && token [0] == '&' - || lastWritten == LastWritten.QuestionMark && token [0] == '?' - || lastWritten == LastWritten.Division && token [0] == '*') { - formatter.Space(); - } - formatter.WriteToken(token); - if (token == "+") { - lastWritten = LastWritten.Plus; - } else if (token == "-") { - lastWritten = LastWritten.Minus; - } else if (token == "&") { - lastWritten = LastWritten.Ampersand; - } else if (token == "?") { - lastWritten = LastWritten.QuestionMark; - } else if (token == "/") { - lastWritten = LastWritten.Division; - } else { - lastWritten = LastWritten.Other; - } + writer.WriteToken(tokenRole, token); + isAtStartOfLine = false; } void LPar() @@ -396,29 +262,78 @@ void Semicolon() void Space(bool addSpace = true) { if (addSpace) { - formatter.Space(); - lastWritten = LastWritten.Whitespace; + writer.Space(); } } void NewLine() { - formatter.NewLine(); - lastWritten = LastWritten.Whitespace; + writer.NewLine(); + isAtStartOfLine = true; } void OpenBrace(BraceStyle style) { - WriteSpecialsUpToRole(Roles.LBrace); - formatter.OpenBrace(style); - lastWritten = LastWritten.Other; + switch (style) { + case BraceStyle.DoNotChange: + case BraceStyle.EndOfLine: + case BraceStyle.BannerStyle: + if (!isAtStartOfLine) + writer.Space(); + writer.WriteToken(Roles.LBrace, "{"); + break; + case BraceStyle.EndOfLineWithoutSpace: + writer.WriteToken(Roles.LBrace, "{"); + break; + case BraceStyle.NextLine: + if (!isAtStartOfLine) + NewLine(); + writer.WriteToken(Roles.LBrace, "{"); + break; + case BraceStyle.NextLineShifted: + NewLine(); + writer.Indent(); + writer.WriteToken(Roles.LBrace, "{"); + NewLine(); + return; + case BraceStyle.NextLineShifted2: + NewLine(); + writer.Indent(); + writer.WriteToken(Roles.LBrace, "{"); + break; + default: + throw new ArgumentOutOfRangeException (); + } + writer.Indent(); + NewLine(); } void CloseBrace(BraceStyle style) { - WriteSpecialsUpToRole(Roles.RBrace); - formatter.CloseBrace(style); - lastWritten = LastWritten.Other; + switch (style) { + case BraceStyle.DoNotChange: + case BraceStyle.EndOfLine: + case BraceStyle.EndOfLineWithoutSpace: + case BraceStyle.NextLine: + writer.Unindent(); + writer.WriteToken(Roles.RBrace, "}"); + isAtStartOfLine = false; + break; + case BraceStyle.BannerStyle: + case BraceStyle.NextLineShifted: + writer.WriteToken(Roles.RBrace, "}"); + isAtStartOfLine = false; + writer.Unindent(); + break; + case BraceStyle.NextLineShifted2: + writer.Unindent(); + writer.WriteToken(Roles.RBrace, "}"); + isAtStartOfLine = false; + writer.Unindent(); + break; + default: + throw new ArgumentOutOfRangeException(); + } } #endregion @@ -502,17 +417,10 @@ void WriteQualifiedIdentifier(IEnumerable identifiers) foreach (Identifier ident in identifiers) { if (first) { first = false; - if (lastWritten == LastWritten.KeywordOrIdentifier) { - formatter.Space(); - } } else { - WriteSpecialsUpToRole(Roles.Dot, ident); - formatter.WriteToken("."); - lastWritten = LastWritten.Other; + writer.WriteToken(Roles.Dot, "."); } - WriteSpecialsUpToNode(ident); - formatter.WriteIdentifier(ident.Name); - lastWritten = LastWritten.KeywordOrIdentifier; + writer.WriteIdentifier(ident); } } @@ -527,9 +435,9 @@ void WriteEmbeddedStatement(Statement embeddedStatement) VisitBlockStatement(block); } else { NewLine(); - formatter.Indent(); + writer.Indent(); embeddedStatement.AcceptVisitor(this); - formatter.Unindent(); + writer.Unindent(); } } @@ -625,8 +533,8 @@ public void VisitArrayInitializerExpression(ArrayInitializerExpression arrayInit // The output visitor will output nested braces only if they are necessary, // or if the braces tokens exist in the AST. bool bracesAreOptional = arrayInitializerExpression.Elements.Count == 1 - && IsObjectOrCollectionInitializer(arrayInitializerExpression.Parent) - && !CanBeConfusedWithObjectInitializer(arrayInitializerExpression.Elements.Single()); + && IsObjectOrCollectionInitializer(arrayInitializerExpression.Parent) + && !CanBeConfusedWithObjectInitializer(arrayInitializerExpression.Elements.Single()); if (bracesAreOptional && arrayInitializerExpression.LBraceToken.IsNull) { arrayInitializerExpression.Elements.Single().AcceptVisitor(this); } else { @@ -667,6 +575,7 @@ void PrintInitializerElements(AstNodeCollection elements) } OpenBrace(style); bool isFirst = true; + AstNode last = null; foreach (AstNode node in elements) { if (isFirst) { isFirst = false; @@ -674,9 +583,11 @@ void PrintInitializerElements(AstNodeCollection elements) Comma(node, noSpaceAfterComma: true); NewLine(); } + last = node; node.AcceptVisitor(this); } - OptionalComma(); + if (last != null) + OptionalComma(last.NextSibling); NewLine(); CloseBrace(style); } @@ -843,7 +754,7 @@ public void VisitDirectionExpression(DirectionExpression directionExpression) public void VisitIdentifierExpression(IdentifierExpression identifierExpression) { StartNode(identifierExpression); - WriteIdentifier(identifierExpression.Identifier); + WriteIdentifier(identifierExpression.IdentifierToken); WriteTypeArguments(identifierExpression.TypeArguments); EndNode(identifierExpression); } @@ -909,7 +820,7 @@ public void VisitMemberReferenceExpression(MemberReferenceExpression memberRefer StartNode(memberReferenceExpression); memberReferenceExpression.Target.AcceptVisitor(this); WriteToken(Roles.Dot); - WriteIdentifier(memberReferenceExpression.MemberName); + WriteIdentifier(memberReferenceExpression.MemberNameToken); WriteTypeArguments(memberReferenceExpression.TypeArguments); EndNode(memberReferenceExpression); } @@ -917,7 +828,7 @@ public void VisitMemberReferenceExpression(MemberReferenceExpression memberRefer public void VisitNamedArgumentExpression(NamedArgumentExpression namedArgumentExpression) { StartNode(namedArgumentExpression); - namedArgumentExpression.NameToken.AcceptVisitor(this); + WriteIdentifier(namedArgumentExpression.NameToken); WriteToken(Roles.Colon); Space(); namedArgumentExpression.Expression.AcceptVisitor(this); @@ -927,7 +838,7 @@ public void VisitNamedArgumentExpression(NamedArgumentExpression namedArgumentEx public void VisitNamedExpression(NamedExpression namedExpression) { StartNode(namedExpression); - namedExpression.NameToken.AcceptVisitor(this); + WriteIdentifier(namedExpression.NameToken); Space(); WriteToken(Roles.Assign); Space(); @@ -938,7 +849,7 @@ public void VisitNamedExpression(NamedExpression namedExpression) public void VisitNullReferenceExpression(NullReferenceExpression nullReferenceExpression) { StartNode(nullReferenceExpression); - WriteKeyword("null", nullReferenceExpression.Role); + writer.WritePrimitiveValue(null); EndNode(nullReferenceExpression); } @@ -984,7 +895,7 @@ public void VisitPointerReferenceExpression(PointerReferenceExpression pointerRe StartNode(pointerReferenceExpression); pointerReferenceExpression.Target.AcceptVisitor(this); WriteToken(PointerReferenceExpression.ArrowRole); - WriteIdentifier(pointerReferenceExpression.MemberName); + WriteIdentifier(pointerReferenceExpression.MemberNameToken); WriteTypeArguments(pointerReferenceExpression.TypeArguments); EndNode(pointerReferenceExpression); } @@ -993,184 +904,9 @@ public void VisitPointerReferenceExpression(PointerReferenceExpression pointerRe public void VisitPrimitiveExpression(PrimitiveExpression primitiveExpression) { StartNode(primitiveExpression); - if (!string.IsNullOrEmpty(primitiveExpression.LiteralValue)) { - formatter.WriteToken(primitiveExpression.LiteralValue); - } else { - WritePrimitiveValue(primitiveExpression.Value); - } + writer.WritePrimitiveValue(primitiveExpression.Value, primitiveExpression.UnsafeLiteralValue); EndNode(primitiveExpression); } - - public static string PrintPrimitiveValue(object val) - { - StringWriter writer = new StringWriter(); - CSharpOutputVisitor visitor = new CSharpOutputVisitor(writer, new CSharpFormattingOptions()); - visitor.WritePrimitiveValue(val); - return writer.ToString(); - } - - void WritePrimitiveValue(object val) - { - if (val == null) { - // usually NullReferenceExpression should be used for this, but we'll handle it anyways - WriteKeyword("null"); - return; - } - - if (val is bool) { - if ((bool)val) { - WriteKeyword("true"); - } else { - WriteKeyword("false"); - } - return; - } - - if (val is string) { - formatter.WriteToken("\"" + ConvertString(val.ToString()) + "\""); - lastWritten = LastWritten.Other; - } else if (val is char) { - formatter.WriteToken("'" + ConvertCharLiteral((char)val) + "'"); - lastWritten = LastWritten.Other; - } else if (val is decimal) { - formatter.WriteToken(((decimal)val).ToString(NumberFormatInfo.InvariantInfo) + "m"); - lastWritten = LastWritten.Other; - } else if (val is float) { - float f = (float)val; - if (float.IsInfinity(f) || float.IsNaN(f)) { - // Strictly speaking, these aren't PrimitiveExpressions; - // but we still support writing these to make life easier for code generators. - WriteKeyword("float"); - WriteToken(Roles.Dot); - if (float.IsPositiveInfinity(f)) { - WriteIdentifier("PositiveInfinity"); - } else if (float.IsNegativeInfinity(f)) { - WriteIdentifier("NegativeInfinity"); - } else { - WriteIdentifier("NaN"); - } - return; - } - if (f == 0 && 1 / f == float.NegativeInfinity) { - // negative zero is a special case - // (again, not a primitive expression, but it's better to handle - // the special case here than to do it in all code generators) - formatter.WriteToken("-"); - } - formatter.WriteToken(f.ToString("R", NumberFormatInfo.InvariantInfo) + "f"); - lastWritten = LastWritten.Other; - } else if (val is double) { - double f = (double)val; - if (double.IsInfinity(f) || double.IsNaN(f)) { - // Strictly speaking, these aren't PrimitiveExpressions; - // but we still support writing these to make life easier for code generators. - WriteKeyword("double"); - WriteToken(Roles.Dot); - if (double.IsPositiveInfinity(f)) { - WriteIdentifier("PositiveInfinity"); - } else if (double.IsNegativeInfinity(f)) { - WriteIdentifier("NegativeInfinity"); - } else { - WriteIdentifier("NaN"); - } - return; - } - if (f == 0 && 1 / f == double.NegativeInfinity) { - // negative zero is a special case - // (again, not a primitive expression, but it's better to handle - // the special case here than to do it in all code generators) - formatter.WriteToken("-"); - } - string number = f.ToString("R", NumberFormatInfo.InvariantInfo); - if (number.IndexOf('.') < 0 && number.IndexOf('E') < 0) { - number += ".0"; - } - formatter.WriteToken(number); - // needs space if identifier follows number; this avoids mistaking the following identifier as type suffix - lastWritten = LastWritten.KeywordOrIdentifier; - } else if (val is IFormattable) { - StringBuilder b = new StringBuilder (); - // if (primitiveExpression.LiteralFormat == LiteralFormat.HexadecimalNumber) { - // b.Append("0x"); - // b.Append(((IFormattable)val).ToString("x", NumberFormatInfo.InvariantInfo)); - // } else { - b.Append(((IFormattable)val).ToString(null, NumberFormatInfo.InvariantInfo)); - // } - if (val is uint || val is ulong) { - b.Append("u"); - } - if (val is long || val is ulong) { - b.Append("L"); - } - formatter.WriteToken(b.ToString()); - // needs space if identifier follows number; this avoids mistaking the following identifier as type suffix - lastWritten = LastWritten.KeywordOrIdentifier; - } else { - formatter.WriteToken(val.ToString()); - lastWritten = LastWritten.Other; - } - } - - static string ConvertCharLiteral(char ch) - { - if (ch == '\'') { - return "\\'"; - } - return ConvertChar(ch); - } - - /// - /// Gets the escape sequence for the specified character. - /// - /// This method does not convert ' or ". - public static string ConvertChar(char ch) - { - switch (ch) { - case '\\': - return "\\\\"; - case '\0': - return "\\0"; - case '\a': - return "\\a"; - case '\b': - return "\\b"; - case '\f': - return "\\f"; - case '\n': - return "\\n"; - case '\r': - return "\\r"; - case '\t': - return "\\t"; - case '\v': - return "\\v"; - default: - if (char.IsControl(ch) || char.IsSurrogate(ch) || - // print all uncommon white spaces as numbers - (char.IsWhiteSpace(ch) && ch != ' ')) { - return "\\u" + ((int)ch).ToString("x4"); - } else { - return ch.ToString(); - } - } - } - - /// - /// Converts special characters to escape sequences within the given string. - /// - public static string ConvertString(string str) - { - StringBuilder sb = new StringBuilder (); - foreach (char ch in str) { - if (ch == '"') { - sb.Append("\\\""); - } else { - sb.Append(ConvertChar(ch)); - } - } - return sb.ToString(); - } - #endregion public void VisitSizeOfExpression(SizeOfExpression sizeOfExpression) @@ -1261,7 +997,7 @@ public void VisitQueryExpression(QueryExpression queryExpression) StartNode(queryExpression); bool indent = queryExpression.Parent is QueryClause && !(queryExpression.Parent is QueryContinuationClause); if (indent) { - formatter.Indent(); + writer.Indent(); NewLine(); } bool first = true; @@ -1276,7 +1012,7 @@ public void VisitQueryExpression(QueryExpression queryExpression) clause.AcceptVisitor(this); } if (indent) { - formatter.Unindent(); + writer.Unindent(); } EndNode(queryExpression); } @@ -1288,7 +1024,7 @@ public void VisitQueryContinuationClause(QueryContinuationClause queryContinuati Space(); WriteKeyword(QueryContinuationClause.IntoKeywordRole); Space(); - queryContinuationClause.IdentifierToken.AcceptVisitor(this); + WriteIdentifier(queryContinuationClause.IdentifierToken); EndNode(queryContinuationClause); } @@ -1298,7 +1034,7 @@ public void VisitQueryFromClause(QueryFromClause queryFromClause) WriteKeyword(QueryFromClause.FromKeywordRole); queryFromClause.Type.AcceptVisitor(this); Space(); - queryFromClause.IdentifierToken.AcceptVisitor(this); + WriteIdentifier(queryFromClause.IdentifierToken); Space(); WriteKeyword(QueryFromClause.InKeywordRole); Space(); @@ -1311,7 +1047,7 @@ public void VisitQueryLetClause(QueryLetClause queryLetClause) StartNode(queryLetClause); WriteKeyword(QueryLetClause.LetKeywordRole); Space(); - queryLetClause.IdentifierToken.AcceptVisitor(this); + WriteIdentifier(queryLetClause.IdentifierToken); Space(policy.SpaceAroundAssignment); WriteToken(Roles.Assign); Space(policy.SpaceAroundAssignment); @@ -1334,7 +1070,7 @@ public void VisitQueryJoinClause(QueryJoinClause queryJoinClause) WriteKeyword(QueryJoinClause.JoinKeywordRole); queryJoinClause.Type.AcceptVisitor(this); Space(); - WriteIdentifier(queryJoinClause.JoinIdentifier, QueryJoinClause.JoinIdentifierRole); + WriteIdentifier(queryJoinClause.JoinIdentifierToken); Space(); WriteKeyword(QueryJoinClause.InKeywordRole); Space(); @@ -1350,7 +1086,7 @@ public void VisitQueryJoinClause(QueryJoinClause queryJoinClause) if (queryJoinClause.IsGroupJoin) { Space(); WriteKeyword(QueryJoinClause.IntoKeywordRole); - WriteIdentifier(queryJoinClause.IntoIdentifier, QueryJoinClause.IntoIdentifierRole); + WriteIdentifier(queryJoinClause.IntoIdentifierToken); } EndNode(queryJoinClause); } @@ -1422,7 +1158,7 @@ public void VisitAttributeSection(AttributeSection attributeSection) StartNode(attributeSection); WriteToken(Roles.LBracket); if (!string.IsNullOrEmpty(attributeSection.AttributeTarget)) { - WriteToken(attributeSection.AttributeTarget, Roles.AttributeTargetRole); + WriteIdentifier(attributeSection.AttributeTargetToken); WriteToken(Roles.Colon); Space(); } @@ -1444,7 +1180,7 @@ public void VisitDelegateDeclaration(DelegateDeclaration delegateDeclaration) WriteKeyword(Roles.DelegateKeyword); delegateDeclaration.ReturnType.AcceptVisitor(this); Space(); - delegateDeclaration.NameToken.AcceptVisitor(this); + WriteIdentifier(delegateDeclaration.NameToken); WriteTypeParameters(delegateDeclaration.TypeParameters); Space(policy.SpaceBeforeDelegateDeclarationParentheses); WriteCommaSeparatedListInParenthesis(delegateDeclaration.Parameters, policy.SpaceWithinMethodDeclarationParentheses); @@ -1466,7 +1202,7 @@ public void VisitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration) MaybeNewLinesAfterUsings(member); } CloseBrace(policy.NamespaceBraceStyle); - OptionalSemicolon(); + OptionalSemicolon(namespaceDeclaration.LastChild); NewLine(); EndNode(namespaceDeclaration); } @@ -1495,7 +1231,7 @@ public void VisitTypeDeclaration(TypeDeclaration typeDeclaration) braceStyle = policy.ClassBraceStyle; break; } - typeDeclaration.NameToken.AcceptVisitor(this); + WriteIdentifier(typeDeclaration.NameToken); WriteTypeParameters(typeDeclaration.TypeParameters); if (typeDeclaration.BaseTypes.Any()) { Space(); @@ -1509,6 +1245,7 @@ public void VisitTypeDeclaration(TypeDeclaration typeDeclaration) OpenBrace(braceStyle); if (typeDeclaration.ClassType == ClassType.Enum) { bool first = true; + AstNode last = null; foreach (var member in typeDeclaration.Members) { if (first) { first = false; @@ -1516,9 +1253,11 @@ public void VisitTypeDeclaration(TypeDeclaration typeDeclaration) Comma(member, noSpaceAfterComma: true); NewLine(); } + last = member; member.AcceptVisitor(this); } - OptionalComma(); + if (last != null) + OptionalComma(last.NextSibling); NewLine(); } else { bool first = true; @@ -1532,7 +1271,7 @@ public void VisitTypeDeclaration(TypeDeclaration typeDeclaration) } } CloseBrace(braceStyle); - OptionalSemicolon(); + OptionalSemicolon(typeDeclaration.LastChild); NewLine(); EndNode(typeDeclaration); } @@ -1541,7 +1280,7 @@ public void VisitUsingAliasDeclaration(UsingAliasDeclaration usingAliasDeclarati { StartNode(usingAliasDeclaration); WriteKeyword(UsingAliasDeclaration.UsingKeywordRole); - WriteIdentifier(usingAliasDeclaration.Alias, UsingAliasDeclaration.AliasRole); + WriteIdentifier(usingAliasDeclaration.GetChildByRole(UsingAliasDeclaration.AliasRole)); Space(policy.SpaceAroundEqualityOperator); WriteToken(Roles.Assign); Space(policy.SpaceAroundEqualityOperator); @@ -1566,7 +1305,7 @@ public void VisitExternAliasDeclaration(ExternAliasDeclaration externAliasDeclar Space(); WriteKeyword(Roles.AliasKeyword); Space(); - externAliasDeclaration.NameToken.AcceptVisitor(this); + WriteIdentifier(externAliasDeclaration.NameToken); Semicolon(); EndNode(externAliasDeclaration); } @@ -1605,19 +1344,16 @@ public void VisitBlockStatement(BlockStatement blockStatement) foreach (var node in blockStatement.Statements) { node.AcceptVisitor(this); } - AstNode pos = positionStack.Pop(); - WriteSpecials(pos, null); - containerStack.Pop(); + EndNode(blockStatement); CloseBrace(style); if (!(blockStatement.Parent is Expression)) NewLine(); - formatter.EndNode(blockStatement); } public void VisitBreakStatement(BreakStatement breakStatement) { StartNode(breakStatement); - WriteKeyword("break"); + WriteKeyword("break", BreakStatement.BreakKeywordRole); Semicolon(); EndNode(breakStatement); } @@ -1633,7 +1369,7 @@ public void VisitCheckedStatement(CheckedStatement checkedStatement) public void VisitContinueStatement(ContinueStatement continueStatement) { StartNode(continueStatement); - WriteKeyword("continue"); + WriteKeyword("continue", ContinueStatement.ContinueKeywordRole); Semicolon(); EndNode(continueStatement); } @@ -1694,7 +1430,7 @@ public void VisitForeachStatement(ForeachStatement foreachStatement) Space(policy.SpacesWithinForeachParentheses); foreachStatement.VariableType.AcceptVisitor(this); Space(); - foreachStatement.VariableNameToken.AcceptVisitor(this); + WriteIdentifier(foreachStatement.VariableNameToken); WriteKeyword(ForeachStatement.InKeywordRole); Space(); foreachStatement.InExpression.AcceptVisitor(this); @@ -1755,7 +1491,7 @@ public void VisitGotoStatement(GotoStatement gotoStatement) { StartNode(gotoStatement); WriteKeyword(GotoStatement.GotoKeywordRole); - WriteIdentifier(gotoStatement.Label); + WriteIdentifier(gotoStatement.GetChildByRole(Roles.Identifier)); Semicolon(); EndNode(gotoStatement); } @@ -1781,7 +1517,7 @@ public void VisitIfElseStatement(IfElseStatement ifElseStatement) public void VisitLabelStatement(LabelStatement labelStatement) { StartNode(labelStatement); - WriteIdentifier(labelStatement.Label); + WriteIdentifier(labelStatement.GetChildByRole(Roles.Identifier)); WriteToken(Roles.Colon); bool foundLabelledStatement = false; for (AstNode tmp = labelStatement.NextSibling; tmp != null; tmp = tmp.NextSibling) { @@ -1835,7 +1571,7 @@ public void VisitSwitchStatement(SwitchStatement switchStatement) RPar(); OpenBrace(policy.StatementBraceStyle); if (!policy.IndentSwitchBody) { - formatter.Unindent(); + writer.Unindent(); } foreach (var section in switchStatement.SwitchSections) { @@ -1843,7 +1579,7 @@ public void VisitSwitchStatement(SwitchStatement switchStatement) } if (!policy.IndentSwitchBody) { - formatter.Indent(); + writer.Indent(); } CloseBrace(policy.StatementBraceStyle); NewLine(); @@ -1863,7 +1599,7 @@ public void VisitSwitchSection(SwitchSection switchSection) } bool isBlock = switchSection.Statements.Count == 1 && switchSection.Statements.Single() is BlockStatement; if (policy.IndentCaseBody && !isBlock) { - formatter.Indent(); + writer.Indent(); } if (!isBlock) @@ -1874,7 +1610,7 @@ public void VisitSwitchSection(SwitchSection switchSection) } if (policy.IndentCaseBody && !isBlock) { - formatter.Unindent(); + writer.Unindent(); } EndNode(switchSection); @@ -1932,7 +1668,7 @@ public void VisitCatchClause(CatchClause catchClause) catchClause.Type.AcceptVisitor(this); if (!string.IsNullOrEmpty(catchClause.VariableName)) { Space(); - catchClause.VariableNameToken.AcceptVisitor(this); + WriteIdentifier(catchClause.VariableNameToken); } Space(policy.SpacesWithinCatchParentheses); RPar(); @@ -2029,13 +1765,13 @@ public void VisitAccessor(Accessor accessor) WriteAttributes(accessor.Attributes); WriteModifiers(accessor.ModifierTokens); if (accessor.Role == PropertyDeclaration.GetterRole) { - WriteKeyword("get"); + WriteKeyword("get", PropertyDeclaration.GetKeywordRole); } else if (accessor.Role == PropertyDeclaration.SetterRole) { - WriteKeyword("set"); + WriteKeyword("set", PropertyDeclaration.SetKeywordRole); } else if (accessor.Role == CustomEventDeclaration.AddAccessorRole) { - WriteKeyword("add"); + WriteKeyword("add", CustomEventDeclaration.AddKeywordRole); } else if (accessor.Role == CustomEventDeclaration.RemoveAccessorRole) { - WriteKeyword("remove"); + WriteKeyword("remove", CustomEventDeclaration.RemoveKeywordRole); } WriteMethodBody(accessor.Body); EndNode(accessor); @@ -2047,12 +1783,10 @@ public void VisitConstructorDeclaration(ConstructorDeclaration constructorDeclar WriteAttributes(constructorDeclaration.Attributes); WriteModifiers(constructorDeclaration.ModifierTokens); TypeDeclaration type = constructorDeclaration.Parent as TypeDeclaration; - var nameToken = constructorDeclaration.NameToken; - if (!nameToken.IsNull) - StartNode(nameToken); - WriteIdentifier(type != null ? type.Name : constructorDeclaration.Name); - if (!nameToken.IsNull) - EndNode(nameToken); + if (type != null && type.Name != constructorDeclaration.Name) + WriteIdentifier((Identifier)type.NameToken.Clone()); + else + WriteIdentifier(constructorDeclaration.NameToken); Space(policy.SpaceBeforeConstructorDeclarationParentheses); WriteCommaSeparatedListInParenthesis(constructorDeclaration.Parameters, policy.SpaceWithinMethodDeclarationParentheses); if (!constructorDeclaration.Initializer.IsNull) { @@ -2085,12 +1819,10 @@ public void VisitDestructorDeclaration(DestructorDeclaration destructorDeclarati WriteModifiers(destructorDeclaration.ModifierTokens); WriteToken(DestructorDeclaration.TildeRole); TypeDeclaration type = destructorDeclaration.Parent as TypeDeclaration; - var nameToken = destructorDeclaration.NameToken; - if (!nameToken.IsNull) - StartNode(nameToken); - WriteIdentifier(type != null ? type.Name : destructorDeclaration.Name); - if (!nameToken.IsNull) - EndNode(nameToken); + if (type != null && type.Name != destructorDeclaration.Name) + WriteIdentifier((Identifier)type.NameToken.Clone()); + else + WriteIdentifier(destructorDeclaration.NameToken); Space(policy.SpaceBeforeConstructorDeclarationParentheses); LPar(); RPar(); @@ -2103,7 +1835,7 @@ public void VisitEnumMemberDeclaration(EnumMemberDeclaration enumMemberDeclarati StartNode(enumMemberDeclaration); WriteAttributes(enumMemberDeclaration.Attributes); WriteModifiers(enumMemberDeclaration.ModifierTokens); - enumMemberDeclaration.NameToken.AcceptVisitor(this); + WriteIdentifier(enumMemberDeclaration.NameToken); if (!enumMemberDeclaration.Initializer.IsNull) { Space(policy.SpaceAroundAssignment); WriteToken(Roles.Assign); @@ -2135,7 +1867,7 @@ public void VisitCustomEventDeclaration(CustomEventDeclaration customEventDeclar customEventDeclaration.ReturnType.AcceptVisitor(this); Space(); WritePrivateImplementationType(customEventDeclaration.PrivateImplementationType); - customEventDeclaration.NameToken.AcceptVisitor(this); + WriteIdentifier(customEventDeclaration.NameToken); OpenBrace(policy.EventBraceStyle); // output add/remove in their original order foreach (AstNode node in customEventDeclaration.Children) { @@ -2177,7 +1909,7 @@ public void VisitFixedFieldDeclaration(FixedFieldDeclaration fixedFieldDeclarati public void VisitFixedVariableInitializer(FixedVariableInitializer fixedVariableInitializer) { StartNode(fixedVariableInitializer); - fixedVariableInitializer.NameToken.AcceptVisitor(this); + WriteIdentifier(fixedVariableInitializer.NameToken); if (!fixedVariableInitializer.CountExpression.IsNull) { WriteToken(Roles.LBracket); Space(policy.SpacesWithinBrackets); @@ -2219,7 +1951,7 @@ public void VisitMethodDeclaration(MethodDeclaration methodDeclaration) methodDeclaration.ReturnType.AcceptVisitor(this); Space(); WritePrivateImplementationType(methodDeclaration.PrivateImplementationType); - methodDeclaration.NameToken.AcceptVisitor(this); + WriteIdentifier(methodDeclaration.NameToken); WriteTypeParameters(methodDeclaration.TypeParameters); Space(policy.SpaceBeforeMethodDeclarationParentheses); WriteCommaSeparatedListInParenthesis(methodDeclaration.Parameters, policy.SpaceWithinMethodDeclarationParentheses); @@ -2245,7 +1977,7 @@ public void VisitOperatorDeclaration(OperatorDeclaration operatorDeclaration) WriteKeyword(OperatorDeclaration.OperatorKeywordRole); Space(); if (operatorDeclaration.OperatorType == OperatorType.Explicit - || operatorDeclaration.OperatorType == OperatorType.Implicit) { + || operatorDeclaration.OperatorType == OperatorType.Implicit) { operatorDeclaration.ReturnType.AcceptVisitor(this); } else { WriteToken(OperatorDeclaration.GetToken(operatorDeclaration.OperatorType), OperatorDeclaration.GetRole(operatorDeclaration.OperatorType)); @@ -2279,7 +2011,7 @@ public void VisitParameterDeclaration(ParameterDeclaration parameterDeclaration) Space(); } if (!string.IsNullOrEmpty(parameterDeclaration.Name)) { - parameterDeclaration.NameToken.AcceptVisitor(this); + WriteIdentifier(parameterDeclaration.NameToken); } if (!parameterDeclaration.DefaultExpression.IsNull) { Space(policy.SpaceAroundAssignment); @@ -2298,7 +2030,7 @@ public void VisitPropertyDeclaration(PropertyDeclaration propertyDeclaration) propertyDeclaration.ReturnType.AcceptVisitor(this); Space(); WritePrivateImplementationType(propertyDeclaration.PrivateImplementationType); - propertyDeclaration.NameToken.AcceptVisitor(this); + WriteIdentifier(propertyDeclaration.NameToken); OpenBrace(policy.PropertyBraceStyle); // output get/set in their original order foreach (AstNode node in propertyDeclaration.Children) { @@ -2317,7 +2049,7 @@ public void VisitPropertyDeclaration(PropertyDeclaration propertyDeclaration) public void VisitVariableInitializer(VariableInitializer variableInitializer) { StartNode(variableInitializer); - variableInitializer.NameToken.AcceptVisitor(this); + WriteIdentifier(variableInitializer.NameToken); if (!variableInitializer.Initializer.IsNull) { Space(policy.SpaceAroundAssignment); WriteToken(Roles.Assign); @@ -2351,7 +2083,7 @@ public void VisitSyntaxTree(SyntaxTree syntaxTree) public void VisitSimpleType(SimpleType simpleType) { StartNode(simpleType); - WriteIdentifier(simpleType.Identifier); + WriteIdentifier(simpleType.IdentifierToken); WriteTypeArguments(simpleType.TypeArguments); EndNode(simpleType); } @@ -2365,7 +2097,7 @@ public void VisitMemberType(MemberType memberType) } else { WriteToken(Roles.Dot); } - WriteIdentifier(memberType.MemberName); + WriteIdentifier(memberType.MemberNameToken); WriteTypeArguments(memberType.TypeArguments); EndNode(memberType); } @@ -2391,9 +2123,7 @@ public void VisitArraySpecifier(ArraySpecifier arraySpecifier) StartNode(arraySpecifier); WriteToken(Roles.LBracket); foreach (var comma in arraySpecifier.GetChildrenByRole(Roles.Comma)) { - WriteSpecialsUpToNode(comma); - formatter.WriteToken(","); - lastWritten = LastWritten.Other; + writer.WriteToken(Roles.Comma, ","); } WriteToken(Roles.RBracket); EndNode(arraySpecifier); @@ -2402,26 +2132,15 @@ public void VisitArraySpecifier(ArraySpecifier arraySpecifier) public void VisitPrimitiveType(PrimitiveType primitiveType) { StartNode(primitiveType); - WriteKeyword(primitiveType.Keyword); - if (primitiveType.Keyword == "new") { - // new() constraint - LPar(); - RPar(); - } + writer.WritePrimitiveType(primitiveType.Keyword); EndNode(primitiveType); } public void VisitComment(Comment comment) { - if (lastWritten == LastWritten.Division) { - // When there's a comment starting after a division operator - // "1.0 / /*comment*/a", then we need to insert a space in front of the comment. - formatter.Space(); - } - formatter.StartNode(comment); - formatter.WriteComment(comment.CommentType, comment.Content); - formatter.EndNode(comment); - lastWritten = LastWritten.Whitespace; + writer.StartNode(comment); + writer.WriteComment(comment.CommentType, comment.Content); + writer.EndNode(comment); } public void VisitNewLine(NewLineNode newLineNode) @@ -2443,10 +2162,9 @@ public void VisitText(TextNode textNode) public void VisitPreProcessorDirective(PreProcessorDirective preProcessorDirective) { - formatter.StartNode(preProcessorDirective); - formatter.WritePreProcessorDirective(preProcessorDirective.Type, preProcessorDirective.Argument); - formatter.EndNode(preProcessorDirective); - lastWritten = LastWritten.Whitespace; + writer.StartNode(preProcessorDirective); + writer.WritePreProcessorDirective(preProcessorDirective.Type, preProcessorDirective.Argument); + writer.EndNode(preProcessorDirective); } public void VisitTypeParameterDeclaration(TypeParameterDeclaration typeParameterDeclaration) @@ -2465,7 +2183,7 @@ public void VisitTypeParameterDeclaration(TypeParameterDeclaration typeParameter default: throw new NotSupportedException ("Invalid value for VarianceModifier"); } - typeParameterDeclaration.NameToken.AcceptVisitor(this); + WriteIdentifier(typeParameterDeclaration.NameToken); EndNode(typeParameterDeclaration); } @@ -2474,7 +2192,7 @@ public void VisitConstraint(Constraint constraint) StartNode(constraint); Space(); WriteKeyword(Roles.WhereKeyword); - WriteIdentifier(constraint.TypeParameter.Identifier); + constraint.TypeParameter.AcceptVisitor(this); Space(); WriteToken(Roles.Colon); Space(); @@ -2486,9 +2204,9 @@ public void VisitCSharpTokenNode(CSharpTokenNode cSharpTokenNode) { CSharpModifierToken mod = cSharpTokenNode as CSharpModifierToken; if (mod != null) { - StartNode(mod); - WriteKeyword(CSharpModifierToken.GetModifierName(mod.Modifier)); - EndNode(mod); + // ITokenWriter assumes that each node processed between a + // StartNode(parentNode)-EndNode(parentNode)-pair is a child of parentNode. + WriteKeyword(CSharpModifierToken.GetModifierName(mod.Modifier), cSharpTokenNode.Role); } else { throw new NotSupportedException ("Should never visit individual tokens"); } @@ -2496,9 +2214,10 @@ public void VisitCSharpTokenNode(CSharpTokenNode cSharpTokenNode) public void VisitIdentifier(Identifier identifier) { - StartNode(identifier); - WriteIdentifier(identifier.Name); - EndNode(identifier); + // Do not call StartNode and EndNode for Identifier, because they are handled by the ITokenWriter. + // ITokenWriter assumes that each node processed between a + // StartNode(parentNode)-EndNode(parentNode)-pair is a child of parentNode. + WriteIdentifier(identifier); } #endregion @@ -2541,7 +2260,7 @@ void VisitChoice(Choice choice) Space(); LPar(); NewLine(); - formatter.Indent(); + writer.Indent(); foreach (INode alternative in choice) { VisitNodeInPattern(alternative); if (alternative != choice.Last()) { @@ -2549,7 +2268,7 @@ void VisitChoice(Choice choice) } NewLine(); } - formatter.Unindent(); + writer.Unindent(); RPar(); } @@ -2603,7 +2322,7 @@ void VisitNodeInPattern(INode childNode) } else if (childNode is Repeat) { VisitRepeat((Repeat)childNode); } else { - WritePrimitiveValue(childNode); + TextWriterTokenWriter.PrintPrimitiveValue(childNode); } } #endregion @@ -2641,7 +2360,7 @@ public void VisitDocumentationReference(DocumentationReference documentationRefe } break; default: - WriteIdentifier(documentationReference.MemberName); + WriteIdentifier(documentationReference.GetChildByRole(Roles.Identifier)); break; } WriteTypeArguments(documentationReference.TypeArguments); diff --git a/ICSharpCode.NRefactory.CSharp/OutputVisitor/IOutputFormatter.cs b/ICSharpCode.NRefactory.CSharp/OutputVisitor/IOutputFormatter.cs deleted file mode 100644 index f780a8ef0..000000000 --- a/ICSharpCode.NRefactory.CSharp/OutputVisitor/IOutputFormatter.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team -// -// 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; - -namespace ICSharpCode.NRefactory.CSharp -{ - /// - /// Output formatter for the Output visitor. - /// - public interface IOutputFormatter - { - void StartNode(AstNode node); - void EndNode(AstNode node); - - /// - /// Writes an identifier. - /// If the identifier conflicts with a keyword, the output visitor will - /// call WriteToken("@") before calling WriteIdentifier(). - /// - void WriteIdentifier(string identifier); - - /// - /// Writes a keyword to the output. - /// - void WriteKeyword(string keyword); - - /// - /// Writes a token to the output. - /// - void WriteToken(string token); - void Space(); - - void OpenBrace(BraceStyle style); - void CloseBrace(BraceStyle style); - - void Indent(); - void Unindent(); - - void NewLine(); - - void WriteComment(CommentType commentType, string content); - void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument); - } -} diff --git a/ICSharpCode.NRefactory.CSharp/OutputVisitor/ITokenWriter.cs b/ICSharpCode.NRefactory.CSharp/OutputVisitor/ITokenWriter.cs new file mode 100644 index 000000000..31b73f987 --- /dev/null +++ b/ICSharpCode.NRefactory.CSharp/OutputVisitor/ITokenWriter.cs @@ -0,0 +1,161 @@ +// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team +// +// 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.IO; + +namespace ICSharpCode.NRefactory.CSharp +{ + public abstract class TokenWriter + { + public abstract void StartNode(AstNode node); + public abstract void EndNode(AstNode node); + + /// + /// Writes an identifier. + /// + public abstract void WriteIdentifier(Identifier identifier); + + /// + /// Writes a keyword to the output. + /// + public abstract void WriteKeyword(Role role, string keyword); + + /// + /// Writes a token to the output. + /// + public abstract void WriteToken(Role role, string token); + + /// + /// Writes a primitive/literal value + /// + public abstract void WritePrimitiveValue(object value, string literalValue = null); + + public abstract void WritePrimitiveType(string type); + + public abstract void Space(); + public abstract void Indent(); + public abstract void Unindent(); + public abstract void NewLine(); + + public abstract void WriteComment(CommentType commentType, string content); + public abstract void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument); + + public static TokenWriter Create(TextWriter writer, string indentation = "\t") + { + return new InsertSpecialsDecorator(new InsertRequiredSpacesDecorator(new TextWriterTokenWriter(writer) { IndentationString = indentation })); + } + + public static TokenWriter CreateWriterThatSetsLocationsInAST(TextWriter writer, string indentation = "\t") + { + var target = new TextWriterTokenWriter(writer) { IndentationString = indentation }; + return new InsertSpecialsDecorator(new InsertRequiredSpacesDecorator(new InsertMissingTokensDecorator(target, target))); + } + + public static TokenWriter WrapInWriterThatSetsLocationsInAST(TokenWriter writer) + { + if (!(writer is ILocatable)) + throw new InvalidOperationException("writer does not provide locations!"); + return new InsertSpecialsDecorator(new InsertRequiredSpacesDecorator(new InsertMissingTokensDecorator(writer, (ILocatable)writer))); + } + } + + public interface ILocatable + { + TextLocation Location { get; } + } + + public abstract class DecoratingTokenWriter : TokenWriter + { + TokenWriter decoratedWriter; + + protected DecoratingTokenWriter(TokenWriter decoratedWriter) + { + if (decoratedWriter == null) + throw new ArgumentNullException("decoratedWriter"); + this.decoratedWriter = decoratedWriter; + } + + public override void StartNode(AstNode node) + { + decoratedWriter.StartNode(node); + } + + public override void EndNode(AstNode node) + { + decoratedWriter.EndNode(node); + } + + public override void WriteIdentifier(Identifier identifier) + { + decoratedWriter.WriteIdentifier(identifier); + } + + public override void WriteKeyword(Role role, string keyword) + { + decoratedWriter.WriteKeyword(role, keyword); + } + + public override void WriteToken(Role role, string token) + { + decoratedWriter.WriteToken(role, token); + } + + public override void WritePrimitiveValue(object value, string literalValue = null) + { + decoratedWriter.WritePrimitiveValue(value, literalValue); + } + + public override void WritePrimitiveType(string type) + { + decoratedWriter.WritePrimitiveType(type); + } + + public override void Space() + { + decoratedWriter.Space(); + } + + public override void Indent() + { + decoratedWriter.Indent(); + } + + public override void Unindent() + { + decoratedWriter.Unindent(); + } + + public override void NewLine() + { + decoratedWriter.NewLine(); + } + + public override void WriteComment(CommentType commentType, string content) + { + decoratedWriter.WriteComment(commentType, content); + } + + public override void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument) + { + decoratedWriter.WritePreProcessorDirective(type, argument); + } + } +} + + diff --git a/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertMissingTokensDecorator.cs b/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertMissingTokensDecorator.cs new file mode 100644 index 000000000..4f390d92d --- /dev/null +++ b/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertMissingTokensDecorator.cs @@ -0,0 +1,122 @@ +// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team +// +// 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.Collections.Generic; +using System.Linq; + +namespace ICSharpCode.NRefactory.CSharp +{ + class InsertMissingTokensDecorator : DecoratingTokenWriter + { + readonly Stack> nodes = new Stack>(); + List currentList; + readonly ILocatable locationProvider; + + public InsertMissingTokensDecorator(TokenWriter writer, ILocatable locationProvider) + : base(writer) + { + this.locationProvider = locationProvider; + currentList = new List(); + } + + public override void StartNode(AstNode node) + { + currentList.Add(node); + nodes.Push(currentList); + currentList = new List(); + base.StartNode(node); + } + + public override void EndNode(AstNode node) + { + System.Diagnostics.Debug.Assert(currentList != null); + foreach (var removable in node.Children.Where(n => n is CSharpTokenNode)) { + removable.Remove(); + } + foreach (var child in currentList) { + System.Diagnostics.Debug.Assert(child.Parent == null || node == child.Parent); + child.Remove(); + node.AddChildWithExistingRole(child); + } + currentList = nodes.Pop(); + base.EndNode(node); + } + + public override void WriteToken(Role role, string token) + { + CSharpTokenNode t = new CSharpTokenNode(locationProvider.Location, (TokenRole)role); + EmptyStatement node = nodes.Peek().LastOrDefault() as EmptyStatement; + if (node == null) + currentList.Add(t); + else { + node.Location = locationProvider.Location; + } + base.WriteToken(role, token); + } + + public override void WriteKeyword(Role role, string keyword) + { + TextLocation start = locationProvider.Location; + CSharpTokenNode t = null; + if (role is TokenRole) + t = new CSharpTokenNode(start, (TokenRole)role); + else if (role == EntityDeclaration.ModifierRole) + t = new CSharpModifierToken(start, CSharpModifierToken.GetModifierValue(keyword)); + else if (keyword == "this") { + ThisReferenceExpression node = nodes.Peek().LastOrDefault() as ThisReferenceExpression; + if (node != null) + node.Location = start; + } else if (keyword == "base") { + BaseReferenceExpression node = nodes.Peek().LastOrDefault() as BaseReferenceExpression; + if (node != null) + node.Location = start; + } + if (t != null) currentList.Add(t); + base.WriteKeyword(role, keyword); + } + + public override void WriteIdentifier(Identifier identifier) + { + if (!identifier.IsNull) + identifier.SetStartLocation(locationProvider.Location); + currentList.Add(identifier); + base.WriteIdentifier(identifier); + } + + public override void WritePrimitiveValue(object value, string literalValue = null) + { + Expression node = nodes.Peek().LastOrDefault() as Expression; + if (node is PrimitiveExpression) { + ((PrimitiveExpression)node).SetStartLocation(locationProvider.Location); + } + if (node is NullReferenceExpression) { + ((NullReferenceExpression)node).SetStartLocation(locationProvider.Location); + } + base.WritePrimitiveValue(value, literalValue); + } + + public override void WritePrimitiveType(string type) + { + PrimitiveType node = nodes.Peek().LastOrDefault() as PrimitiveType; + if (node != null) + node.SetStartLocation(locationProvider.Location); + base.WritePrimitiveType(type); + } + } +} diff --git a/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertRequiredSpacesDecorator.cs b/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertRequiredSpacesDecorator.cs new file mode 100644 index 000000000..8cb616df3 --- /dev/null +++ b/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertRequiredSpacesDecorator.cs @@ -0,0 +1,184 @@ +// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team +// +// 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.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ICSharpCode.NRefactory.PatternMatching; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.CSharp; + +namespace ICSharpCode.NRefactory.CSharp +{ + class InsertRequiredSpacesDecorator : DecoratingTokenWriter + { + /// + /// Used to insert the minimal amount of spaces so that the lexer recognizes the tokens that were written. + /// + LastWritten lastWritten; + + enum LastWritten + { + Whitespace, + Other, + KeywordOrIdentifier, + Plus, + Minus, + Ampersand, + QuestionMark, + Division + } + + public InsertRequiredSpacesDecorator(TokenWriter writer) + : base(writer) + { + } + + public override void WriteIdentifier(Identifier identifier) + { + if (identifier.IsVerbatim) { + if (lastWritten == LastWritten.KeywordOrIdentifier) { + // this space is not strictly required, so we call Space() + Space(); + } + } else if (lastWritten == LastWritten.KeywordOrIdentifier) { + // this space is strictly required, so we directly call the formatter + base.Space(); + } + base.WriteIdentifier(identifier); + lastWritten = LastWritten.KeywordOrIdentifier; + } + + public override void WriteKeyword(Role role, string keyword) + { + if (lastWritten == LastWritten.KeywordOrIdentifier) { + Space(); + } + base.WriteKeyword(role, keyword); + lastWritten = LastWritten.KeywordOrIdentifier; + } + + public override void WriteToken(Role role, string token) + { + // Avoid that two +, - or ? tokens are combined into a ++, -- or ?? token. + // Note that we don't need to handle tokens like = because there's no valid + // C# program that contains the single token twice in a row. + // (for +, - and &, this can happen with unary operators; + // for ?, this can happen in "a is int? ? b : c" or "a as int? ?? 0"; + // and for /, this can happen with "1/ *ptr" or "1/ //comment".) + if (lastWritten == LastWritten.Plus && token[0] == '+' || + lastWritten == LastWritten.Minus && token[0] == '-' || + lastWritten == LastWritten.Ampersand && token[0] == '&' || + lastWritten == LastWritten.QuestionMark && token[0] == '?' || + lastWritten == LastWritten.Division && token[0] == '*') { + base.Space(); + } + base.WriteToken(role, token); + if (token == "+") { + lastWritten = LastWritten.Plus; + } else if (token == "-") { + lastWritten = LastWritten.Minus; + } else if (token == "&") { + lastWritten = LastWritten.Ampersand; + } else if (token == "?") { + lastWritten = LastWritten.QuestionMark; + } else if (token == "/") { + lastWritten = LastWritten.Division; + } else { + lastWritten = LastWritten.Other; + } + } + + public override void Space() + { + base.Space(); + lastWritten = LastWritten.Whitespace; + } + + public override void NewLine() + { + base.NewLine(); + lastWritten = LastWritten.Whitespace; + } + + public override void WriteComment(CommentType commentType, string content) + { + if (lastWritten == LastWritten.Division) { + // When there's a comment starting after a division operator + // "1.0 / /*comment*/a", then we need to insert a space in front of the comment. + base.Space(); + } + base.WriteComment(commentType, content); + lastWritten = LastWritten.Whitespace; + } + + public override void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument) + { + base.WritePreProcessorDirective(type, argument); + lastWritten = LastWritten.Whitespace; + } + + public override void WritePrimitiveValue(object value, string literalValue = null) + { + base.WritePrimitiveValue(value, literalValue); + if (value == null || value is bool) + return; + if (value is string) { + lastWritten = LastWritten.Other; + } else if (value is char) { + lastWritten = LastWritten.Other; + } else if (value is decimal) { + lastWritten = LastWritten.Other; + } else if (value is float) { + float f = (float)value; + if (float.IsInfinity(f) || float.IsNaN(f)) return; + lastWritten = LastWritten.Other; + } else if (value is double) { + double f = (double)value; + if (double.IsInfinity(f) || double.IsNaN(f)) return; + // needs space if identifier follows number; + // this avoids mistaking the following identifier as type suffix + lastWritten = LastWritten.KeywordOrIdentifier; + } else if (value is IFormattable) { + // needs space if identifier follows number; + // this avoids mistaking the following identifier as type suffix + lastWritten = LastWritten.KeywordOrIdentifier; + } else { + lastWritten = LastWritten.Other; + } + } + + public override void WritePrimitiveType(string type) + { + if (lastWritten == LastWritten.KeywordOrIdentifier) { + Space(); + } + base.WritePrimitiveType(type); + if (type == "new") { + lastWritten = LastWritten.Other; + } else { + lastWritten = LastWritten.KeywordOrIdentifier; + } + } + } +} \ No newline at end of file diff --git a/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertSpecialsDecorator.cs b/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertSpecialsDecorator.cs new file mode 100644 index 000000000..0fafdeef0 --- /dev/null +++ b/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertSpecialsDecorator.cs @@ -0,0 +1,157 @@ +// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team +// +// 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.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using ICSharpCode.NRefactory.CSharp; + +namespace ICSharpCode.NRefactory.CSharp +{ + class InsertSpecialsDecorator : DecoratingTokenWriter + { + readonly Stack positionStack = new Stack(); + int visitorWroteNewLine = 0; + + public InsertSpecialsDecorator(TokenWriter writer) : base(writer) + { + } + + public override void StartNode(AstNode node) + { + if (positionStack.Count > 0) { + WriteSpecialsUpToNode(node); + } + positionStack.Push(node.FirstChild); + base.StartNode(node); + } + + public override void EndNode(AstNode node) + { + base.EndNode(node); + AstNode pos = positionStack.Pop(); + Debug.Assert(pos == null || pos.Parent == node); + WriteSpecials(pos, null); + } + + public override void WriteKeyword(Role role, string keyword) + { + if (role != null) { + WriteSpecialsUpToRole(role); + } + base.WriteKeyword(role, keyword); + } + + public override void WriteIdentifier(Identifier identifier) + { + WriteSpecialsUpToRole(identifier.Role ?? Roles.Identifier); + base.WriteIdentifier(identifier); + } + + public override void WriteToken(Role role, string token) + { + WriteSpecialsUpToRole(role); + base.WriteToken(role, token); + } + + public override void NewLine() + { + if (visitorWroteNewLine >= 0) + base.NewLine(); + visitorWroteNewLine++; + } + + #region WriteSpecials + /// + /// Writes all specials from start to end (exclusive). Does not touch the positionStack. + /// + void WriteSpecials(AstNode start, AstNode end) + { + for (AstNode pos = start; pos != end; pos = pos.NextSibling) { + if (pos.Role == Roles.Comment) { + var node = (Comment)pos; + base.WriteComment(node.CommentType, node.Content); + } + // see CSharpOutputVisitor.VisitNewLine() + // if (pos.Role == Roles.NewLine) { + // if (visitorWroteNewLine <= 0) + // base.NewLine(); + // visitorWroteNewLine--; + // } + if (pos.Role == Roles.PreProcessorDirective) { + var node = (PreProcessorDirective)pos; + base.WritePreProcessorDirective(node.Type, node.Argument); + } + } + } + + /// + /// Writes all specials between the current position (in the positionStack) and the next + /// node with the specified role. Advances the current position. + /// + void WriteSpecialsUpToRole(Role role) + { + WriteSpecialsUpToRole(role, null); + } + + void WriteSpecialsUpToRole(Role role, AstNode nextNode) + { + if (positionStack.Count == 0) { + return; + } + // Look for the role between the current position and the nextNode. + for (AstNode pos = positionStack.Peek(); pos != null && pos != nextNode; pos = pos.NextSibling) { + if (pos.Role == role) { + WriteSpecials(positionStack.Pop(), pos); + // Push the next sibling because the node matching the role is not a special, + // and should be considered to be already handled. + positionStack.Push(pos.NextSibling); + // This is necessary for OptionalComma() to work correctly. + break; + } + } + } + + /// + /// Writes all specials between the current position (in the positionStack) and the specified node. + /// Advances the current position. + /// + void WriteSpecialsUpToNode(AstNode node) + { + if (positionStack.Count == 0) { + return; + } + for (AstNode pos = positionStack.Peek(); pos != null; pos = pos.NextSibling) { + if (pos == node) { + WriteSpecials(positionStack.Pop(), pos); + // Push the next sibling because the node itself is not a special, + // and should be considered to be already handled. + positionStack.Push(pos.NextSibling); + // This is necessary for OptionalComma() to work correctly. + break; + } + } + } + #endregion + } +} + + + + diff --git a/ICSharpCode.NRefactory.CSharp/OutputVisitor/TextWriterOutputFormatter.cs b/ICSharpCode.NRefactory.CSharp/OutputVisitor/TextWriterOutputFormatter.cs index 4d1a0d364..4b9a262c6 100644 --- a/ICSharpCode.NRefactory.CSharp/OutputVisitor/TextWriterOutputFormatter.cs +++ b/ICSharpCode.NRefactory.CSharp/OutputVisitor/TextWriterOutputFormatter.cs @@ -17,139 +17,79 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Globalization; using System.IO; +using System.Text; namespace ICSharpCode.NRefactory.CSharp { /// /// Writes C# code into a TextWriter. /// - public class TextWriterOutputFormatter : IOutputFormatter + public class TextWriterTokenWriter : TokenWriter, ILocatable { readonly TextWriter textWriter; int indentation; bool needsIndent = true; bool isAtStartOfLine = true; + int line, column; public int Indentation { - get { - return this.indentation; - } - set { - this.indentation = value; - } + get { return this.indentation; } + set { this.indentation = value; } + } + + public TextLocation Location { + get { return new TextLocation(line, column + (needsIndent ? indentation * IndentationString.Length : 0)); } } public string IndentationString { get; set; } - public TextWriterOutputFormatter(TextWriter textWriter) + public TextWriterTokenWriter(TextWriter textWriter) { if (textWriter == null) throw new ArgumentNullException("textWriter"); this.textWriter = textWriter; this.IndentationString = "\t"; + this.line = 1; + this.column = 1; } - public void WriteIdentifier(string ident) + public override void WriteIdentifier(Identifier identifier) { WriteIndentation(); - textWriter.Write(ident); + if (identifier.IsVerbatim) { + textWriter.Write('@'); + column++; + } + textWriter.Write(identifier.Name); + column += identifier.Name.Length; isAtStartOfLine = false; } - public void WriteKeyword(string keyword) + public override void WriteKeyword(Role role, string keyword) { WriteIndentation(); + column += keyword.Length; textWriter.Write(keyword); isAtStartOfLine = false; } - public void WriteToken(string token) + public override void WriteToken(Role role, string token) { WriteIndentation(); + column += token.Length; textWriter.Write(token); isAtStartOfLine = false; } - public void Space() + public override void Space() { WriteIndentation(); + column++; textWriter.Write(' '); } - public void OpenBrace(BraceStyle style) - { - switch (style) { - case BraceStyle.DoNotChange: - case BraceStyle.EndOfLine: - case BraceStyle.BannerStyle: - WriteIndentation(); - if (!isAtStartOfLine) - textWriter.Write(' '); - textWriter.Write('{'); - break; - case BraceStyle.EndOfLineWithoutSpace: - WriteIndentation(); - textWriter.Write('{'); - break; - case BraceStyle.NextLine: - if (!isAtStartOfLine) - NewLine(); - WriteIndentation(); - textWriter.Write('{'); - break; - - case BraceStyle.NextLineShifted: - NewLine (); - Indent(); - WriteIndentation(); - textWriter.Write('{'); - NewLine(); - return; - case BraceStyle.NextLineShifted2: - NewLine (); - Indent(); - WriteIndentation(); - textWriter.Write('{'); - break; - default: - throw new ArgumentOutOfRangeException (); - } - Indent(); - NewLine(); - } - - public void CloseBrace(BraceStyle style) - { - switch (style) { - case BraceStyle.DoNotChange: - case BraceStyle.EndOfLine: - case BraceStyle.EndOfLineWithoutSpace: - case BraceStyle.NextLine: - Unindent(); - WriteIndentation(); - textWriter.Write('}'); - isAtStartOfLine = false; - break; - case BraceStyle.BannerStyle: - case BraceStyle.NextLineShifted: - WriteIndentation(); - textWriter.Write('}'); - isAtStartOfLine = false; - Unindent(); - break; - case BraceStyle.NextLineShifted2: - Unindent(); - WriteIndentation(); - textWriter.Write('}'); - isAtStartOfLine = false; - Unindent(); - break; - default: - throw new ArgumentOutOfRangeException (); - } - } - protected void WriteIndentation() { if (needsIndent) { @@ -157,33 +97,37 @@ protected void WriteIndentation() for (int i = 0; i < indentation; i++) { textWriter.Write(this.IndentationString); } + column += indentation * IndentationString.Length; } } - public void NewLine() + public override void NewLine() { textWriter.WriteLine(); + column = 1; + line++; needsIndent = true; isAtStartOfLine = true; } - public void Indent() + public override void Indent() { indentation++; } - public void Unindent() + public override void Unindent() { indentation--; } - public void WriteComment(CommentType commentType, string content) + public override void WriteComment(CommentType commentType, string content) { WriteIndentation(); switch (commentType) { case CommentType.SingleLine: textWriter.Write("//"); textWriter.WriteLine(content); + column += 2 + content.Length; needsIndent = true; isAtStartOfLine = true; break; @@ -191,36 +135,263 @@ public void WriteComment(CommentType commentType, string content) textWriter.Write("/*"); textWriter.Write(content); textWriter.Write("*/"); + column += 2; + UpdateEndLocation(content, ref line, ref column); + column += 2; isAtStartOfLine = false; break; case CommentType.Documentation: textWriter.Write("///"); textWriter.WriteLine(content); + column += 3 + content.Length; needsIndent = true; isAtStartOfLine = true; break; default: textWriter.Write(content); + column += content.Length; break; } } - public void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument) + static void UpdateEndLocation(string content, ref int line, ref int column) + { + if (string.IsNullOrEmpty(content)) + return; + for (int i = 0; i < content.Length; i++) { + char ch = content[i]; + switch (ch) { + case '\r': + if (i + 1 < content.Length && content[i + 1] == '\n') + i++; + goto case '\n'; + case '\n': + line++; + column = 0; + break; + } + column++; + } + } + + public override void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument) { // pre-processor directive must start on its own line if (!isAtStartOfLine) NewLine(); WriteIndentation(); textWriter.Write('#'); - textWriter.Write(type.ToString().ToLowerInvariant()); + string directive = type.ToString().ToLowerInvariant(); + textWriter.Write(directive); + column += 1 + directive.Length; if (!string.IsNullOrEmpty(argument)) { textWriter.Write(' '); textWriter.Write(argument); + column += 1 + argument.Length; } NewLine(); } - public virtual void StartNode(AstNode node) + public static string PrintPrimitiveValue(object value) + { + TextWriter writer = new StringWriter(); + TextWriterTokenWriter tokenWriter = new TextWriterTokenWriter(writer); + tokenWriter.WritePrimitiveValue(value); + return writer.ToString(); + } + + public override void WritePrimitiveValue(object value, string literalValue = null) + { + if (literalValue != null) { + textWriter.Write(literalValue); + column += literalValue.Length; + return; + } + + if (value == null) { + // usually NullReferenceExpression should be used for this, but we'll handle it anyways + textWriter.Write("null"); + column += 4; + return; + } + + if (value is bool) { + if ((bool)value) { + textWriter.Write("true"); + column += 4; + } else { + textWriter.Write("false"); + column += 5; + } + return; + } + + if (value is string) { + string tmp = "\"" + ConvertString(value.ToString()) + "\""; + column += tmp.Length; + textWriter.Write(tmp); + } else if (value is char) { + string tmp = "'" + ConvertCharLiteral((char)value) + "'"; + column += tmp.Length; + textWriter.Write(tmp); + } else if (value is decimal) { + string str = ((decimal)value).ToString(NumberFormatInfo.InvariantInfo) + "m"; + column += str.Length; + textWriter.Write(str); + } else if (value is float) { + float f = (float)value; + if (float.IsInfinity(f) || float.IsNaN(f)) { + // Strictly speaking, these aren't PrimitiveExpressions; + // but we still support writing these to make life easier for code generators. + textWriter.Write("float"); + column += 5; + WriteToken(Roles.Dot, "."); + if (float.IsPositiveInfinity(f)) { + textWriter.Write("PositiveInfinity"); + column += "PositiveInfinity".Length; + } else if (float.IsNegativeInfinity(f)) { + textWriter.Write("NegativeInfinity"); + column += "NegativeInfinity".Length; + } else { + textWriter.Write("NaN"); + column += 3; + } + return; + } + if (f == 0 && 1 / f == float.NegativeInfinity) { + // negative zero is a special case + // (again, not a primitive expression, but it's better to handle + // the special case here than to do it in all code generators) + textWriter.Write("-"); + column++; + } + var str = f.ToString("R", NumberFormatInfo.InvariantInfo) + "f"; + column += str.Length; + textWriter.Write(str); + } else if (value is double) { + double f = (double)value; + if (double.IsInfinity(f) || double.IsNaN(f)) { + // Strictly speaking, these aren't PrimitiveExpressions; + // but we still support writing these to make life easier for code generators. + textWriter.Write("double"); + column += 6; + WriteToken(Roles.Dot, "."); + if (double.IsPositiveInfinity(f)) { + textWriter.Write("PositiveInfinity"); + column += "PositiveInfinity".Length; + } else if (double.IsNegativeInfinity(f)) { + textWriter.Write("NegativeInfinity"); + column += "NegativeInfinity".Length; + } else { + textWriter.Write("NaN"); + column += 3; + } + return; + } + if (f == 0 && 1 / f == double.NegativeInfinity) { + // negative zero is a special case + // (again, not a primitive expression, but it's better to handle + // the special case here than to do it in all code generators) + textWriter.Write("-"); + } + string number = f.ToString("R", NumberFormatInfo.InvariantInfo); + if (number.IndexOf('.') < 0 && number.IndexOf('E') < 0) { + number += ".0"; + } + textWriter.Write(number); + } else if (value is IFormattable) { + StringBuilder b = new StringBuilder (); +// if (primitiveExpression.LiteralFormat == LiteralFormat.HexadecimalNumber) { +// b.Append("0x"); +// b.Append(((IFormattable)val).ToString("x", NumberFormatInfo.InvariantInfo)); +// } else { + b.Append(((IFormattable)value).ToString(null, NumberFormatInfo.InvariantInfo)); +// } + if (value is uint || value is ulong) { + b.Append("u"); + } + if (value is long || value is ulong) { + b.Append("L"); + } + textWriter.Write(b.ToString()); + column += b.Length; + } else { + textWriter.Write(value.ToString()); + column += value.ToString().Length; + } + } + + static string ConvertCharLiteral(char ch) + { + if (ch == '\'') { + return "\\'"; + } + return ConvertChar(ch); + } + + /// + /// Gets the escape sequence for the specified character. + /// + /// This method does not convert ' or ". + public static string ConvertChar(char ch) + { + switch (ch) { + case '\\': + return "\\\\"; + case '\0': + return "\\0"; + case '\a': + return "\\a"; + case '\b': + return "\\b"; + case '\f': + return "\\f"; + case '\n': + return "\\n"; + case '\r': + return "\\r"; + case '\t': + return "\\t"; + case '\v': + return "\\v"; + default: + if (char.IsControl(ch) || char.IsSurrogate(ch) || + // print all uncommon white spaces as numbers + (char.IsWhiteSpace(ch) && ch != ' ')) { + return "\\u" + ((int)ch).ToString("x4"); + } else { + return ch.ToString(); + } + } + } + + /// + /// Converts special characters to escape sequences within the given string. + /// + public static string ConvertString(string str) + { + StringBuilder sb = new StringBuilder (); + foreach (char ch in str) { + if (ch == '"') { + sb.Append("\\\""); + } else { + sb.Append(ConvertChar(ch)); + } + } + return sb.ToString(); + } + + public override void WritePrimitiveType(string type) + { + textWriter.Write(type); + column += type.Length; + if (type == "new") { + textWriter.Write("()"); + column += 2; + } + } + + public override void StartNode(AstNode node) { // Write out the indentation, so that overrides of this method // can rely use the current output length to identify the position of the node @@ -228,7 +399,7 @@ public virtual void StartNode(AstNode node) WriteIndentation(); } - public virtual void EndNode(AstNode node) + public override void EndNode(AstNode node) { } } diff --git a/ICSharpCode.NRefactory.CSharp/Refactoring/Script.cs b/ICSharpCode.NRefactory.CSharp/Refactoring/Script.cs index d2bfb3dc4..e58caa6dc 100644 --- a/ICSharpCode.NRefactory.CSharp/Refactoring/Script.cs +++ b/ICSharpCode.NRefactory.CSharp/Refactoring/Script.cs @@ -476,18 +476,26 @@ protected virtual int GetIndentLevelAt (int offset) return 0; } - sealed class SegmentTrackingOutputFormatter : TextWriterOutputFormatter + sealed class SegmentTrackingTokenWriter : TextWriterTokenWriter { internal List> NewSegments = new List>(); readonly Stack startOffsets = new Stack(); readonly StringWriter stringWriter; - public SegmentTrackingOutputFormatter (StringWriter stringWriter) + public SegmentTrackingTokenWriter(StringWriter stringWriter) : base(stringWriter) { this.stringWriter = stringWriter; } + public override void WriteIdentifier (Identifier identifier) + { + int startOffset = stringWriter.GetStringBuilder ().Length; + int endOffset = startOffset + (identifier.Name ?? "").Length + (identifier.IsVerbatim ? 1 : 0); + NewSegments.Add(new KeyValuePair(identifier, new Segment(startOffset, endOffset))); + base.WriteIdentifier (identifier); + } + public override void StartNode (AstNode node) { base.StartNode (node); @@ -506,7 +514,7 @@ public override void EndNode (AstNode node) protected NodeOutput OutputNode(int indentLevel, AstNode node, bool startWithNewLine = false) { var stringWriter = new StringWriter (); - var formatter = new SegmentTrackingOutputFormatter (stringWriter); + var formatter = new SegmentTrackingTokenWriter(stringWriter); formatter.Indentation = indentLevel; formatter.IndentationString = Options.TabsToSpaces ? new string (' ', Options.IndentSize) : "\t"; stringWriter.NewLine = Options.EolMarker; diff --git a/ICSharpCode.NRefactory.CSharp/TypeSystem/CSharpUnresolvedFile.cs b/ICSharpCode.NRefactory.CSharp/TypeSystem/CSharpUnresolvedFile.cs index 88f61f1c9..cbff80cc0 100644 --- a/ICSharpCode.NRefactory.CSharp/TypeSystem/CSharpUnresolvedFile.cs +++ b/ICSharpCode.NRefactory.CSharp/TypeSystem/CSharpUnresolvedFile.cs @@ -31,7 +31,7 @@ namespace ICSharpCode.NRefactory.CSharp.TypeSystem /// Represents a file that was parsed and converted for the type system. /// [Serializable, FastSerializerVersion(TypeSystemConvertVisitor.version)] - public sealed class CSharpUnresolvedFile : AbstractFreezable, IUnresolvedFile, IUnresolvedDocumentationProvider + public class CSharpUnresolvedFile : AbstractFreezable, IUnresolvedFile, IUnresolvedDocumentationProvider { // The 'FastSerializerVersion' attribute on CSharpUnresolvedFile must be incremented when fixing // bugs in the TypeSystemConvertVisitor diff --git a/ICSharpCode.NRefactory.ConsistencyCheck/Xml/XmlReaderTest.cs b/ICSharpCode.NRefactory.ConsistencyCheck/Xml/XmlReaderTest.cs index feba28b5c..ea3ed47de 100644 --- a/ICSharpCode.NRefactory.ConsistencyCheck/Xml/XmlReaderTest.cs +++ b/ICSharpCode.NRefactory.ConsistencyCheck/Xml/XmlReaderTest.cs @@ -92,9 +92,9 @@ static string ToString(object val) if (val == null) return "null"; else if (val is string) - return "\"" + CSharpOutputVisitor.ConvertString((string)val) + "\""; + return "\"" + TextWriterTokenWriter.ConvertString((string)val) + "\""; else if (val is char) - return "'" + CSharpOutputVisitor.ConvertChar((char)val) + "'"; + return "'" + TextWriterTokenWriter.ConvertChar((char)val) + "'"; else return val.ToString(); } diff --git a/ICSharpCode.NRefactory.Tests/CSharp/CSharpOutputVisitorTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/CSharpOutputVisitorTests.cs index 218a84bb0..ba4f7ebaa 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/CSharpOutputVisitorTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/CSharpOutputVisitorTests.cs @@ -31,7 +31,7 @@ void AssertOutput(string expected, AstNode node, CSharpFormattingOptions policy policy = FormattingOptionsFactory.CreateMono(); StringWriter w = new StringWriter(); w.NewLine = "\n"; - node.AcceptVisitor(new CSharpOutputVisitor(new TextWriterOutputFormatter(w) { IndentationString = "$" }, policy)); + node.AcceptVisitor(new CSharpOutputVisitor(new TextWriterTokenWriter(w) { IndentationString = "$" }, policy)); Assert.AreEqual(expected.Replace("\r", ""), w.ToString()); } diff --git a/ICSharpCode.NRefactory.Tests/CSharp/InsertMissingTokensDecoratorTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/InsertMissingTokensDecoratorTests.cs new file mode 100644 index 000000000..c0b02722a --- /dev/null +++ b/ICSharpCode.NRefactory.Tests/CSharp/InsertMissingTokensDecoratorTests.cs @@ -0,0 +1,87 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.IO; +using System.Linq; +using System.Text; +using ICSharpCode.NRefactory.Editor; +using NUnit.Framework; +using ICSharpCode.NRefactory.CSharp.Parser; + +namespace ICSharpCode.NRefactory.CSharp +{ + /// + /// Description of InsertMissingTokensDecoratorTests. + /// + [TestFixture] + public class InsertMissingTokensDecoratorTests + { + string[] fileNames; + + [TestFixtureSetUp] + public void SetUp() + { + string path = Path.GetFullPath (Path.Combine ("..", "..")); + if (!File.Exists(Path.Combine(path, "NRefactory.sln"))) + throw new InvalidOperationException("Test cannot find the NRefactory source code in " + path); + fileNames = Directory.GetFiles(path, "*.cs", SearchOption.AllDirectories); + } + + static void RemoveTokens(AstNode node) + { + foreach (var child in node.Descendants) { + if (child is CSharpTokenNode && !(child is CSharpModifierToken)) + child.Remove(); + else if (child.NodeType == NodeType.Whitespace) + child.Remove(); + } + } + + void AssertOutput(AstNode node) + { + RemoveTokens(node); + StringWriter w = new StringWriter(); + w.NewLine = "\n"; + node.AcceptVisitor(new CSharpOutputVisitor(TokenWriter.CreateWriterThatSetsLocationsInAST(w), FormattingOptionsFactory.CreateSharpDevelop())); + var doc = new ReadOnlyDocument(w.ToString()); + ConsistencyChecker.CheckMissingTokens(node, "test.cs", doc); + ConsistencyChecker.CheckPositionConsistency(node, "test.cs", doc); + } + + [Test] + public void SimpleClass() + { + var code = @"class Test +{ +} +"; + var unit = SyntaxTree.Parse(code); + AssertOutput(unit); + } + + [Test] + public void SimpleMethod() + { + var code = @"class Test +{ + void A () + { + } +} +"; + var unit = SyntaxTree.Parse(code); + AssertOutput(unit); + } + + [Test, Ignore] + public void SelfTest() + { + foreach (var file in fileNames) { + Console.WriteLine("processing {0}...", file); + var node = SyntaxTree.Parse(File.ReadAllText(file), file); + AssertOutput(node); + } + } + } +} diff --git a/ICSharpCode.NRefactory.Tests/CSharp/InsertParenthesesVisitorTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/InsertParenthesesVisitorTests.cs index 2ee58946c..88442e725 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/InsertParenthesesVisitorTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/InsertParenthesesVisitorTests.cs @@ -39,7 +39,7 @@ string InsertReadable(Expression expr) expr.AcceptVisitor(new InsertParenthesesVisitor { InsertParenthesesForReadability = true }); StringWriter w = new StringWriter(); w.NewLine = " "; - expr.AcceptVisitor(new CSharpOutputVisitor(new TextWriterOutputFormatter(w) { IndentationString = "" }, policy)); + expr.AcceptVisitor(new CSharpOutputVisitor(new TextWriterTokenWriter(w) { IndentationString = "" }, policy)); return w.ToString(); } @@ -49,7 +49,7 @@ string InsertRequired(Expression expr) expr.AcceptVisitor(new InsertParenthesesVisitor { InsertParenthesesForReadability = false }); StringWriter w = new StringWriter(); w.NewLine = " "; - expr.AcceptVisitor(new CSharpOutputVisitor(new TextWriterOutputFormatter(w) { IndentationString = "" }, policy)); + expr.AcceptVisitor(new CSharpOutputVisitor(new TextWriterTokenWriter(w) { IndentationString = "" }, policy)); return w.ToString(); } diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Parser/ConsistencyChecker.cs b/ICSharpCode.NRefactory.Tests/CSharp/Parser/ConsistencyChecker.cs new file mode 100644 index 000000000..7582bd4d3 --- /dev/null +++ b/ICSharpCode.NRefactory.Tests/CSharp/Parser/ConsistencyChecker.cs @@ -0,0 +1,101 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.IO; +using ICSharpCode.NRefactory.Editor; +using NUnit.Framework; + +namespace ICSharpCode.NRefactory.CSharp.Parser +{ + /// + /// Provides utilities to check whether positions and/or tokens in an AST are valid. + /// + public static class ConsistencyChecker + { + static void PrintNode (AstNode node) + { + Console.WriteLine ("Parent:" + node.GetType ()); + Console.WriteLine ("Children:"); + foreach (var c in node.Children) + Console.WriteLine (c.GetType () +" at:"+ c.StartLocation +"-"+ c.EndLocation + " Role: "+ c.Role); + Console.WriteLine ("----"); + } + + public static void CheckPositionConsistency (AstNode node, string currentFileName, IDocument currentDocument = null) + { + if (currentDocument == null) + currentDocument = new ReadOnlyDocument(File.ReadAllText(currentFileName)); + string comment = "(" + node.GetType ().Name + " at " + node.StartLocation + " in " + currentFileName + ")"; + var pred = node.StartLocation <= node.EndLocation; + if (!pred) + PrintNode (node); + Assert.IsTrue(pred, "StartLocation must be before EndLocation " + comment); + var prevNodeEnd = node.StartLocation; + var prevNode = node; + for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { + bool assertion = child.StartLocation >= prevNodeEnd; + if (!assertion) { + PrintNode (prevNode); + PrintNode (node); + } + Assert.IsTrue(assertion, currentFileName + ": Child " + child.GetType () +" (" + child.StartLocation + ")" +" must start after previous sibling " + prevNode.GetType () + "(" + prevNode.StartLocation + ")"); + CheckPositionConsistency(child, currentFileName, currentDocument); + prevNodeEnd = child.EndLocation; + prevNode = child; + } + Assert.IsTrue(prevNodeEnd <= node.EndLocation, "Last child must end before parent node ends " + comment); + } + + public static void CheckMissingTokens(AstNode node, string currentFileName, IDocument currentDocument = null) + { + if (currentDocument == null) + currentDocument = new ReadOnlyDocument(File.ReadAllText(currentFileName)); + if (IsLeafNode(node)) { + Assert.IsNull(node.FirstChild, "Token nodes should not have children"); + } else { + var prevNodeEnd = node.StartLocation; + var prevNode = node; + for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { + CheckWhitespace(prevNode, prevNodeEnd, child, child.StartLocation, currentFileName, currentDocument); + CheckMissingTokens(child, currentFileName, currentDocument); + prevNode = child; + prevNodeEnd = child.EndLocation; + } + CheckWhitespace(prevNode, prevNodeEnd, node, node.EndLocation, currentFileName, currentDocument); + } + } + + static bool IsLeafNode(AstNode node) + { + + if (node.NodeType == NodeType.Token) + return true; + if (node.NodeType == NodeType.Whitespace) + return !(node is PragmaWarningPreprocessorDirective); + return node is PrimitiveType || node is PrimitiveExpression || node is NullReferenceExpression; + } + + static void CheckWhitespace(AstNode startNode, TextLocation whitespaceStart, AstNode endNode, TextLocation whitespaceEnd, string currentFileName, IDocument currentDocument) + { + if (whitespaceStart == whitespaceEnd || startNode == endNode) + return; + Assert.Greater(whitespaceStart.Line, 0); + Assert.Greater(whitespaceStart.Column, 0); + Assert.Greater(whitespaceEnd.Line, 0); + Assert.Greater(whitespaceEnd.Column, 0); + Assert.IsTrue(whitespaceEnd >= whitespaceStart, endNode.GetType().Name + ".StartLocation < " + startNode.GetType().Name + ".EndLocation: " + whitespaceEnd + " < " + whitespaceStart); + int start = currentDocument.GetOffset(whitespaceStart.Line, whitespaceStart.Column); + int end = currentDocument.GetOffset(whitespaceEnd.Line, whitespaceEnd.Column); + string text = currentDocument.GetText(start, end - start); + bool assertion = string.IsNullOrWhiteSpace(text); + if (!assertion) { + if (startNode.Parent != endNode.Parent) + PrintNode (startNode.Parent); + PrintNode (endNode.Parent); + } + Assert.IsTrue(assertion, "Expected whitespace between " + startNode.GetType () +":" + whitespaceStart + " and " + endNode.GetType () + ":" + whitespaceEnd + + ", but got '" + text + "' (in " + currentFileName + " parent:" + startNode.Parent.GetType () +")"); + } + } +} diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Parser/ParseSelfTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Parser/ParseSelfTests.cs index 149bc13ce..3611594ee 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Parser/ParseSelfTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Parser/ParseSelfTests.cs @@ -67,99 +67,21 @@ public void GenerateTypeSystem() } #region ParseAndCheckPositions - string currentFileName; - ReadOnlyDocument currentDocument; [Test, Ignore("Positions still are incorrect in several cases")] public void ParseAndCheckPositions() { CSharpParser parser = new CSharpParser(); foreach (string fileName in fileNames) { - this.currentDocument = new ReadOnlyDocument(File.ReadAllText(fileName)); + var currentDocument = new ReadOnlyDocument(File.ReadAllText(fileName)); SyntaxTree syntaxTree = parser.Parse(currentDocument, fileName); if (parser.HasErrors) continue; - this.currentFileName = fileName; - CheckPositionConsistency(syntaxTree); - CheckMissingTokens(syntaxTree); + ConsistencyChecker.CheckPositionConsistency(syntaxTree, fileName, currentDocument); + ConsistencyChecker.CheckMissingTokens(syntaxTree, fileName, currentDocument); } } - - void PrintNode (AstNode node) - { - Console.WriteLine ("Parent:" + node.GetType ()); - Console.WriteLine ("Children:"); - foreach (var c in node.Children) - Console.WriteLine (c.GetType () +" at:"+ c.StartLocation +"-"+ c.EndLocation + " Role: "+ c.Role); - Console.WriteLine ("----"); - } - - void CheckPositionConsistency (AstNode node) - { - string comment = "(" + node.GetType ().Name + " at " + node.StartLocation + " in " + currentFileName + ")"; - var pred = node.StartLocation <= node.EndLocation; - if (!pred) - PrintNode (node); - Assert.IsTrue(pred, "StartLocation must be before EndLocation " + comment); - var prevNodeEnd = node.StartLocation; - var prevNode = node; - for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { - bool assertion = child.StartLocation >= prevNodeEnd; - if (!assertion) { - PrintNode (prevNode); - PrintNode (node); - - } - Assert.IsTrue(assertion, currentFileName + ": Child " + child.GetType () +" (" + child.StartLocation + ")" +" must start after previous sibling " + prevNode.GetType () + "(" + prevNode.StartLocation + ")"); - CheckPositionConsistency(child); - prevNodeEnd = child.EndLocation; - prevNode = child; - } - Assert.IsTrue(prevNodeEnd <= node.EndLocation, "Last child must end before parent node ends " + comment); - } - void CheckMissingTokens(AstNode node) - { - if (IsLeafNode(node)) { - Assert.IsNull(node.FirstChild, "Token nodes should not have children"); - } else { - var prevNodeEnd = node.StartLocation; - var prevNode = node; - for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { - CheckWhitespace(prevNode, prevNodeEnd, child, child.StartLocation); - CheckMissingTokens(child); - prevNode = child; - prevNodeEnd = child.EndLocation; - } - CheckWhitespace(prevNode, prevNodeEnd, node, node.EndLocation); - } - } - - bool IsLeafNode(AstNode node) - { - if (node.NodeType == NodeType.Token) - return true; - if (node.NodeType == NodeType.Whitespace) - return !(node is PragmaWarningPreprocessorDirective); - return node is PrimitiveType || node is PrimitiveExpression || node is NullReferenceExpression; - } - - void CheckWhitespace(AstNode startNode, TextLocation whitespaceStart, AstNode endNode, TextLocation whitespaceEnd) - { - if (whitespaceStart == whitespaceEnd) - return; - int start = currentDocument.GetOffset(whitespaceStart.Line, whitespaceStart.Column); - int end = currentDocument.GetOffset(whitespaceEnd.Line, whitespaceEnd.Column); - string text = currentDocument.GetText(start, end - start); - bool assertion = string.IsNullOrWhiteSpace(text); - if (!assertion) { - if (startNode.Parent != endNode.Parent) - PrintNode (startNode.Parent); - PrintNode (endNode.Parent); - } - Assert.IsTrue(assertion, "Expected whitespace between " + startNode.GetType () +":" + whitespaceStart + " and " + endNode.GetType () + ":" + whitespaceEnd - + ", but got '" + text + "' (in " + currentFileName + " parent:" + startNode.Parent.GetType () +")"); - } #endregion } } diff --git a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj index d1d2c8e27..aeb3b3430 100644 --- a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj +++ b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj @@ -156,8 +156,10 @@ + + @@ -617,6 +619,7 @@ {D68133BD-1E63-496E-9EDE-4FBDBF77B486} Mono.Cecil + True {53DCA265-3C3C-42F9-B647-F72BA678122B} diff --git a/ICSharpCode.NRefactory.Xml/DocumentationElement.cs b/ICSharpCode.NRefactory.Xml/DocumentationElement.cs index 43e816b31..6cfd57c4a 100644 --- a/ICSharpCode.NRefactory.Xml/DocumentationElement.cs +++ b/ICSharpCode.NRefactory.Xml/DocumentationElement.cs @@ -46,6 +46,12 @@ public static XmlDocumentationElement Get(IEntity entity, bool inheritDocIfMissi IMember member = entity as IMember; if (inheritDocIfMissing && member != null) { + if (member.SymbolKind == SymbolKind.Constructor) { + // For constructors, the documentation of the base class ctor + // isn't really suitable as constructors are not inherited. + // We'll use the type's documentation instead: + return Get(entity.DeclaringTypeDefinition, inheritDocIfMissing); + } foreach (IMember baseMember in InheritanceHelper.GetBaseMembers(member, includeImplementedInterfaces: true)) { documentationComment = baseMember.Documentation; if (documentationComment != null) @@ -104,6 +110,7 @@ public XmlDocumentationElement(string text, IEntity declaringEntity) { if (text == null) throw new ArgumentNullException("text"); + this.declaringEntity = declaringEntity; this.textContent = text; } @@ -112,7 +119,7 @@ public XmlDocumentationElement(string text, IEntity declaringEntity) /// May return null. /// public IEntity DeclaringEntity { - get { return null; } + get { return declaringEntity; } } IEntity referencedEntity;