From 78e7fc45a912de9d73e6c0f9a0d2aff0f7b37de5 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 26 Jul 2013 20:14:19 +0200 Subject: [PATCH] implement InsertMissingTokensDecorator --- ...tOutputFormatter.cs => TextTokenWriter.cs} | 31 +++-- .../ICSharpCode.Decompiler.csproj | 2 +- .../Ast/AstNode.cs | 16 +++ .../Ast/CSharpModifierToken.cs | 45 ++++++ .../Expressions/NullReferenceExpression.cs | 10 +- .../Ast/Expressions/PrimitiveExpression.cs | 6 + .../Ast/Identifier.cs | 6 + .../Ast/PrimitiveType.cs | 7 + .../ICSharpCode.NRefactory.CSharp.csproj | 1 + .../OutputVisitor/CSharpAmbience.cs | 12 +- .../OutputVisitor/CSharpOutputVisitor.cs | 104 +++++++------- .../OutputVisitor/ITokenWriter.cs | 89 ++++++++---- .../InsertMissingTokensDecorator.cs | 112 +++++++++++++++ .../InsertRequiredSpacesDecorator.cs | 23 +++- .../OutputVisitor/InsertSpecialsDecorator.cs | 2 +- .../TextWriterOutputFormatter.cs | 129 ++++++++++++++---- .../InsertMissingTokensDecoratorTests.cs | 87 ++++++++++++ .../CSharp/Parser/ConsistencyChecker.cs | 90 ++++++++++++ .../CSharp/Parser/ParseSelfTests.cs | 77 +---------- .../ICSharpCode.NRefactory.Tests.csproj | 2 + 20 files changed, 635 insertions(+), 216 deletions(-) rename src/Libraries/ICSharpCode.Decompiler/Ast/{TextOutputFormatter.cs => TextTokenWriter.cs} (92%) create mode 100644 src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertMissingTokensDecorator.cs create mode 100644 src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/InsertMissingTokensDecoratorTests.cs create mode 100644 src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Parser/ConsistencyChecker.cs diff --git a/src/Libraries/ICSharpCode.Decompiler/Ast/TextOutputFormatter.cs b/src/Libraries/ICSharpCode.Decompiler/Ast/TextTokenWriter.cs similarity index 92% rename from src/Libraries/ICSharpCode.Decompiler/Ast/TextOutputFormatter.cs rename to src/Libraries/ICSharpCode.Decompiler/Ast/TextTokenWriter.cs index 08da545bce..3fba3a213b 100644 --- a/src/Libraries/ICSharpCode.Decompiler/Ast/TextOutputFormatter.cs +++ b/src/Libraries/ICSharpCode.Decompiler/Ast/TextTokenWriter.cs @@ -27,7 +27,7 @@ using Mono.Cecil; namespace ICSharpCode.Decompiler.Ast { - public class TextTokenWriter : ITokenWriter + public class TextTokenWriter : TokenWriter { readonly ITextOutput output; readonly Stack nodeStack = new Stack(); @@ -47,7 +47,7 @@ namespace ICSharpCode.Decompiler.Ast this.output = output; } - public void WriteIdentifier(Identifier identifier) + public override void WriteIdentifier(Identifier identifier) { var definition = GetCurrentDefinition(); if (definition != null) { @@ -160,12 +160,12 @@ namespace ICSharpCode.Decompiler.Ast return null; } - public void WriteKeyword(Role role, string keyword) + public override void WriteKeyword(Role role, string keyword) { output.Write(keyword); } - public void WriteToken(Role role, string token) + public override void WriteToken(Role role, string token) { // Attach member reference to token only if there's no identifier in the current node. MemberReference memberRef = GetCurrentMemberReference(); @@ -176,7 +176,7 @@ namespace ICSharpCode.Decompiler.Ast output.Write(token); } - public void Space() + public override void Space() { output.Write(' '); } @@ -203,17 +203,17 @@ namespace ICSharpCode.Decompiler.Ast braceLevelWithinType--; } - public void Indent() + public override void Indent() { output.Indent(); } - public void Unindent() + public override void Unindent() { output.Unindent(); } - public void NewLine() + public override void NewLine() { if (lastUsingDeclaration) { output.MarkFoldEnd(); @@ -223,7 +223,7 @@ namespace ICSharpCode.Decompiler.Ast output.WriteLine(); } - public void WriteComment(CommentType commentType, string content) + public override void WriteComment(CommentType commentType, string content) { switch (commentType) { case CommentType.SingleLine: @@ -255,7 +255,7 @@ namespace ICSharpCode.Decompiler.Ast } } - public void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument) + public override void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument) { // pre-processor directive must start on its own line output.Write('#'); @@ -267,7 +267,12 @@ namespace ICSharpCode.Decompiler.Ast output.WriteLine(); } - public void WritePrimitiveValue(object value) + public override void WritePrimitiveValue(object value, string literalValue = null) + { + + } + + public override void WritePrimitiveType(string type) { } @@ -275,7 +280,7 @@ namespace ICSharpCode.Decompiler.Ast Stack startLocations = new Stack(); Stack symbolsStack = new Stack(); - public void StartNode(AstNode node) + public override void StartNode(AstNode node) { if (nodeStack.Count == 0) { if (IsUsingDeclaration(node)) { @@ -303,7 +308,7 @@ namespace ICSharpCode.Decompiler.Ast return node is UsingDeclaration || node is UsingAliasDeclaration; } - public void EndNode(AstNode node) + public override void EndNode(AstNode node) { if (nodeStack.Pop() != node) throw new InvalidOperationException(); diff --git a/src/Libraries/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/src/Libraries/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 40fa4dc044..12fd59bb91 100644 --- a/src/Libraries/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/src/Libraries/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -60,7 +60,7 @@ - + diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/AstNode.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/AstNode.cs index 7066f6300e..8cce34376d 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/AstNode.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/AstNode.cs @@ -406,6 +406,8 @@ namespace ICSharpCode.NRefactory.CSharp 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) @@ -413,6 +415,20 @@ namespace ICSharpCode.NRefactory.CSharp AddChildUnsafe (child, role); } + public void AddChildWithExistingRole (T child) 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) + throw new ArgumentException ("Cannot add a frozen node.", "child"); + AddChildUnsafe (child, child.Role); + } + /// /// Adds a child without performing any safety checks. /// diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/CSharpModifierToken.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/CSharpModifierToken.cs index ddcd3ff3a9..1a46006f24 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/CSharpModifierToken.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/CSharpModifierToken.cs @@ -169,5 +169,50 @@ namespace ICSharpCode.NRefactory.CSharp 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/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/Expressions/NullReferenceExpression.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/Expressions/NullReferenceExpression.cs index a7a3ab7b5b..fbfeb6f91a 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/Expressions/NullReferenceExpression.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/Expressions/NullReferenceExpression.cs @@ -1,6 +1,6 @@ // // NullReferenceExpression.cs -// +// // Author: // Mike Krüger // @@ -38,6 +38,12 @@ namespace ICSharpCode.NRefactory.CSharp } } + 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 @@ namespace ICSharpCode.NRefactory.CSharp { visitor.VisitNullReferenceExpression (this); } - + public override T AcceptVisitor (IAstVisitor visitor) { return visitor.VisitNullReferenceExpression (this); diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/Expressions/PrimitiveExpression.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/Expressions/PrimitiveExpression.cs index a47906652f..22d83f4b9a 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/Expressions/PrimitiveExpression.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/Expressions/PrimitiveExpression.cs @@ -42,6 +42,12 @@ namespace ICSharpCode.NRefactory.CSharp } } + internal void SetStartLocation(TextLocation value) + { + ThrowIfFrozen(); + this.startLocation = value; + } + string literalValue; TextLocation? endLocation; public override TextLocation EndLocation { diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/Identifier.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/Identifier.cs index fad153b03d..3244e746e0 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/Identifier.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/Identifier.cs @@ -83,6 +83,12 @@ namespace ICSharpCode.NRefactory.CSharp } } + internal void SetStartLocation(TextLocation value) + { + ThrowIfFrozen(); + this.startLocation = value; + } + const uint verbatimBit = 1u << AstNodeFlagsUsedBits; public bool IsVerbatim { diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/PrimitiveType.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/PrimitiveType.cs index 8861e32ee0..5b52a37ac5 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/PrimitiveType.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/PrimitiveType.cs @@ -71,6 +71,13 @@ namespace ICSharpCode.NRefactory.CSharp 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/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj index 3ae0a1f5c5..aeb5d52759 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj @@ -207,6 +207,7 @@ + diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpAmbience.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpAmbience.cs index 4b584a49ce..ee21e19b2a 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpAmbience.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpAmbience.cs @@ -41,7 +41,7 @@ namespace ICSharpCode.NRefactory.CSharp return writer.ToString(); } - public void ConvertEntity(IEntity entity, ITokenWriter writer, CSharpFormattingOptions formattingPolicy) + public void ConvertEntity(IEntity entity, TokenWriter writer, CSharpFormattingOptions formattingPolicy) { if (entity == null) throw new ArgumentNullException("entity"); @@ -160,7 +160,7 @@ namespace ICSharpCode.NRefactory.CSharp return astBuilder; } - void WriteTypeDeclarationName(ITypeDefinition typeDef, ITokenWriter writer, CSharpFormattingOptions formattingPolicy) + void WriteTypeDeclarationName(ITypeDefinition typeDef, TokenWriter writer, CSharpFormattingOptions formattingPolicy) { TypeSystemAstBuilder astBuilder = CreateAstBuilder(); EntityDeclaration node = astBuilder.ConvertEntity(typeDef); @@ -178,7 +178,7 @@ namespace ICSharpCode.NRefactory.CSharp } } - void WriteMemberDeclarationName(IMember member, ITokenWriter writer, CSharpFormattingOptions formattingPolicy) + void WriteMemberDeclarationName(IMember member, TokenWriter writer, CSharpFormattingOptions formattingPolicy) { TypeSystemAstBuilder astBuilder = CreateAstBuilder(); EntityDeclaration node = astBuilder.ConvertEntity(member); @@ -235,7 +235,7 @@ namespace ICSharpCode.NRefactory.CSharp } } - void PrintModifiers(Modifiers modifiers, ITokenWriter writer) + void PrintModifiers(Modifiers modifiers, TokenWriter writer) { foreach (var m in CSharpModifierToken.AllModifiers) { if ((modifiers & m) == m) { @@ -245,7 +245,7 @@ namespace ICSharpCode.NRefactory.CSharp } } - void WriteQualifiedName(string name, ITokenWriter writer, CSharpFormattingOptions formattingPolicy) + void WriteQualifiedName(string name, TokenWriter writer, CSharpFormattingOptions formattingPolicy) { var node = AstType.Create(name); var outputVisitor = new CSharpOutputVisitor(writer, formattingPolicy); @@ -270,7 +270,7 @@ namespace ICSharpCode.NRefactory.CSharp return astType.ToString(); } - public void ConvertType(IType type, ITokenWriter writer, CSharpFormattingOptions formattingPolicy) + public void ConvertType(IType type, TokenWriter writer, CSharpFormattingOptions formattingPolicy) { TypeSystemAstBuilder astBuilder = CreateAstBuilder(); AstType astType = astBuilder.ConvertType(type); diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpOutputVisitor.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpOutputVisitor.cs index dedfa161a6..48aee8dfe5 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -34,7 +34,7 @@ namespace ICSharpCode.NRefactory.CSharp /// public class CSharpOutputVisitor : IAstVisitor { - readonly ITokenWriter writer; + readonly TokenWriter writer; readonly CSharpFormattingOptions policy; readonly Stack containerStack = new Stack (); @@ -46,11 +46,11 @@ namespace ICSharpCode.NRefactory.CSharp if (formattingPolicy == null) { throw new ArgumentNullException ("formattingPolicy"); } - this.writer = new InsertSpecialsDecorator(new InsertRequiredSpacesDecorator(new TextWriterTokenWriter(textWriter))); + this.writer = TokenWriter.Create(textWriter); this.policy = formattingPolicy; } - public CSharpOutputVisitor (ITokenWriter writer, CSharpFormattingOptions formattingPolicy) + public CSharpOutputVisitor (TokenWriter writer, CSharpFormattingOptions formattingPolicy) { if (writer == null) { throw new ArgumentNullException ("writer"); @@ -828,7 +828,7 @@ namespace ICSharpCode.NRefactory.CSharp public void VisitNamedArgumentExpression(NamedArgumentExpression namedArgumentExpression) { StartNode(namedArgumentExpression); - namedArgumentExpression.NameToken.AcceptVisitor(this); + WriteIdentifier(namedArgumentExpression.NameToken); WriteToken(Roles.Colon); Space(); namedArgumentExpression.Expression.AcceptVisitor(this); @@ -838,7 +838,7 @@ namespace ICSharpCode.NRefactory.CSharp public void VisitNamedExpression(NamedExpression namedExpression) { StartNode(namedExpression); - namedExpression.NameToken.AcceptVisitor(this); + WriteIdentifier(namedExpression.NameToken); Space(); WriteToken(Roles.Assign); Space(); @@ -849,7 +849,7 @@ namespace ICSharpCode.NRefactory.CSharp public void VisitNullReferenceExpression(NullReferenceExpression nullReferenceExpression) { StartNode(nullReferenceExpression); - WriteKeyword("null", nullReferenceExpression.Role); + writer.WritePrimitiveValue(null); EndNode(nullReferenceExpression); } @@ -904,11 +904,7 @@ namespace ICSharpCode.NRefactory.CSharp public void VisitPrimitiveExpression(PrimitiveExpression primitiveExpression) { StartNode(primitiveExpression); - if (!string.IsNullOrEmpty(primitiveExpression.LiteralValue)) { - writer.WriteToken(primitiveExpression.Role, primitiveExpression.LiteralValue); - } else { - writer.WritePrimitiveValue(primitiveExpression.Value); - } + writer.WritePrimitiveValue(primitiveExpression.Value, primitiveExpression.LiteralValue); EndNode(primitiveExpression); } #endregion @@ -1028,7 +1024,7 @@ namespace ICSharpCode.NRefactory.CSharp Space(); WriteKeyword(QueryContinuationClause.IntoKeywordRole); Space(); - queryContinuationClause.IdentifierToken.AcceptVisitor(this); + WriteIdentifier(queryContinuationClause.IdentifierToken); EndNode(queryContinuationClause); } @@ -1038,7 +1034,7 @@ namespace ICSharpCode.NRefactory.CSharp WriteKeyword(QueryFromClause.FromKeywordRole); queryFromClause.Type.AcceptVisitor(this); Space(); - queryFromClause.IdentifierToken.AcceptVisitor(this); + WriteIdentifier(queryFromClause.IdentifierToken); Space(); WriteKeyword(QueryFromClause.InKeywordRole); Space(); @@ -1051,7 +1047,7 @@ namespace ICSharpCode.NRefactory.CSharp StartNode(queryLetClause); WriteKeyword(QueryLetClause.LetKeywordRole); Space(); - queryLetClause.IdentifierToken.AcceptVisitor(this); + WriteIdentifier(queryLetClause.IdentifierToken); Space(policy.SpaceAroundAssignment); WriteToken(Roles.Assign); Space(policy.SpaceAroundAssignment); @@ -1184,7 +1180,7 @@ namespace ICSharpCode.NRefactory.CSharp 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); @@ -1234,7 +1230,7 @@ namespace ICSharpCode.NRefactory.CSharp braceStyle = policy.ClassBraceStyle; break; } - typeDeclaration.NameToken.AcceptVisitor(this); + WriteIdentifier(typeDeclaration.NameToken); WriteTypeParameters(typeDeclaration.TypeParameters); if (typeDeclaration.BaseTypes.Any()) { Space(); @@ -1302,7 +1298,7 @@ namespace ICSharpCode.NRefactory.CSharp Space(); WriteKeyword(Roles.AliasKeyword); Space(); - externAliasDeclaration.NameToken.AcceptVisitor(this); + WriteIdentifier(externAliasDeclaration.NameToken); Semicolon(); EndNode(externAliasDeclaration); } @@ -1350,7 +1346,7 @@ namespace ICSharpCode.NRefactory.CSharp public void VisitBreakStatement(BreakStatement breakStatement) { StartNode(breakStatement); - WriteKeyword("break"); + WriteKeyword("break", BreakStatement.BreakKeywordRole); Semicolon(); EndNode(breakStatement); } @@ -1366,7 +1362,7 @@ namespace ICSharpCode.NRefactory.CSharp public void VisitContinueStatement(ContinueStatement continueStatement) { StartNode(continueStatement); - WriteKeyword("continue"); + WriteKeyword("continue", ContinueStatement.ContinueKeywordRole); Semicolon(); EndNode(continueStatement); } @@ -1427,7 +1423,7 @@ namespace ICSharpCode.NRefactory.CSharp Space(policy.SpacesWithinForeachParentheses); foreachStatement.VariableType.AcceptVisitor(this); Space(); - foreachStatement.VariableNameToken.AcceptVisitor(this); + WriteIdentifier(foreachStatement.VariableNameToken); WriteKeyword(ForeachStatement.InKeywordRole); Space(); foreachStatement.InExpression.AcceptVisitor(this); @@ -1665,7 +1661,7 @@ namespace ICSharpCode.NRefactory.CSharp catchClause.Type.AcceptVisitor(this); if (!string.IsNullOrEmpty(catchClause.VariableName)) { Space(); - catchClause.VariableNameToken.AcceptVisitor(this); + WriteIdentifier(catchClause.VariableNameToken); } Space(policy.SpacesWithinCatchParentheses); RPar(); @@ -1762,13 +1758,13 @@ namespace ICSharpCode.NRefactory.CSharp 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); @@ -1780,12 +1776,10 @@ namespace ICSharpCode.NRefactory.CSharp 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.NameToken : constructorDeclaration.NameToken); - 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) { @@ -1818,12 +1812,10 @@ namespace ICSharpCode.NRefactory.CSharp 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.NameToken : destructorDeclaration.NameToken); - 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(); @@ -1836,7 +1828,7 @@ namespace ICSharpCode.NRefactory.CSharp 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); @@ -1868,7 +1860,7 @@ namespace ICSharpCode.NRefactory.CSharp 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) { @@ -1910,7 +1902,7 @@ namespace ICSharpCode.NRefactory.CSharp public void VisitFixedVariableInitializer(FixedVariableInitializer fixedVariableInitializer) { StartNode(fixedVariableInitializer); - fixedVariableInitializer.NameToken.AcceptVisitor(this); + WriteIdentifier(fixedVariableInitializer.NameToken); if (!fixedVariableInitializer.CountExpression.IsNull) { WriteToken(Roles.LBracket); Space(policy.SpacesWithinBrackets); @@ -1952,7 +1944,7 @@ namespace ICSharpCode.NRefactory.CSharp 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); @@ -2012,7 +2004,7 @@ namespace ICSharpCode.NRefactory.CSharp Space(); } if (!string.IsNullOrEmpty(parameterDeclaration.Name)) { - parameterDeclaration.NameToken.AcceptVisitor(this); + WriteIdentifier(parameterDeclaration.NameToken); } if (!parameterDeclaration.DefaultExpression.IsNull) { Space(policy.SpaceAroundAssignment); @@ -2031,7 +2023,7 @@ namespace ICSharpCode.NRefactory.CSharp 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) { @@ -2050,7 +2042,7 @@ namespace ICSharpCode.NRefactory.CSharp public void VisitVariableInitializer(VariableInitializer variableInitializer) { StartNode(variableInitializer); - variableInitializer.NameToken.AcceptVisitor(this); + WriteIdentifier(variableInitializer.NameToken); if (!variableInitializer.Initializer.IsNull) { Space(policy.SpaceAroundAssignment); WriteToken(Roles.Assign); @@ -2120,12 +2112,7 @@ namespace ICSharpCode.NRefactory.CSharp public void VisitPrimitiveType(PrimitiveType primitiveType) { StartNode(primitiveType); - WriteKeyword(primitiveType.Keyword); - if (primitiveType.Keyword == "new") { - // new() constraint - LPar(); - RPar(); - } + writer.WritePrimitiveType(primitiveType.Keyword); EndNode(primitiveType); } @@ -2176,7 +2163,7 @@ namespace ICSharpCode.NRefactory.CSharp default: throw new NotSupportedException ("Invalid value for VarianceModifier"); } - typeParameterDeclaration.NameToken.AcceptVisitor(this); + WriteIdentifier(typeParameterDeclaration.NameToken); EndNode(typeParameterDeclaration); } @@ -2185,7 +2172,7 @@ namespace ICSharpCode.NRefactory.CSharp StartNode(constraint); Space(); WriteKeyword(Roles.WhereKeyword); - WriteIdentifier(constraint.TypeParameter.IdentifierToken); + constraint.TypeParameter.AcceptVisitor(this); Space(); WriteToken(Roles.Colon); Space(); @@ -2197,9 +2184,9 @@ namespace ICSharpCode.NRefactory.CSharp { 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"); } @@ -2207,9 +2194,10 @@ namespace ICSharpCode.NRefactory.CSharp public void VisitIdentifier(Identifier identifier) { - StartNode(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); - EndNode(identifier); } #endregion @@ -2368,4 +2356,4 @@ namespace ICSharpCode.NRefactory.CSharp } #endregion } -} +} \ No newline at end of file diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/ITokenWriter.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/ITokenWriter.cs index f79364ddf0..877f9ca337 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/ITokenWriter.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/ITokenWriter.cs @@ -17,110 +17,141 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.IO; namespace ICSharpCode.NRefactory.CSharp { - public interface ITokenWriter + public abstract class TokenWriter { - void StartNode(AstNode node); - void EndNode(AstNode node); + public abstract void StartNode(AstNode node); + public abstract void EndNode(AstNode node); /// /// Writes an identifier. /// - void WriteIdentifier(Identifier identifier); + public abstract void WriteIdentifier(Identifier identifier); /// /// Writes a keyword to the output. /// - void WriteKeyword(Role role, string keyword); + public abstract void WriteKeyword(Role role, string keyword); /// /// Writes a token to the output. /// - void WriteToken(Role role, string token); + public abstract void WriteToken(Role role, string token); /// /// Writes a primitive/literal value /// - void WritePrimitiveValue(object value); + public abstract void WritePrimitiveValue(object value, string literalValue = null); - void Space(); - void Indent(); - void Unindent(); - void NewLine(); + public abstract void WritePrimitiveType(string type); - void WriteComment(CommentType commentType, string content); - void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument); + 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, string indentation = "\t") + { + 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 : ITokenWriter + public abstract class DecoratingTokenWriter : TokenWriter { - ITokenWriter decoratedWriter; + TokenWriter decoratedWriter; - protected DecoratingTokenWriter(ITokenWriter decoratedWriter) + protected DecoratingTokenWriter(TokenWriter decoratedWriter) { if (decoratedWriter == null) throw new ArgumentNullException("decoratedWriter"); this.decoratedWriter = decoratedWriter; } - public virtual void StartNode(AstNode node) + public override void StartNode(AstNode node) { decoratedWriter.StartNode(node); } - public virtual void EndNode(AstNode node) + public override void EndNode(AstNode node) { decoratedWriter.EndNode(node); } - public virtual void WriteIdentifier(Identifier identifier) + public override void WriteIdentifier(Identifier identifier) { decoratedWriter.WriteIdentifier(identifier); } - public virtual void WriteKeyword(Role role, string keyword) + public override void WriteKeyword(Role role, string keyword) { decoratedWriter.WriteKeyword(role, keyword); } - public virtual void WriteToken(Role role, string token) + public override void WriteToken(Role role, string token) { decoratedWriter.WriteToken(role, token); } - public virtual void WritePrimitiveValue(object value) + public override void WritePrimitiveValue(object value, string literalValue = null) + { + decoratedWriter.WritePrimitiveValue(value, literalValue); + } + + public override void WritePrimitiveType(string type) { - decoratedWriter.WritePrimitiveValue(value); + decoratedWriter.WritePrimitiveType(type); } - public virtual void Space() + public override void Space() { decoratedWriter.Space(); } - public virtual void Indent() + public override void Indent() { decoratedWriter.Indent(); } - public virtual void Unindent() + public override void Unindent() { decoratedWriter.Unindent(); } - public virtual void NewLine() + public override void NewLine() { decoratedWriter.NewLine(); } - public virtual void WriteComment(CommentType commentType, string content) + public override void WriteComment(CommentType commentType, string content) { decoratedWriter.WriteComment(commentType, content); } - public virtual void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument) + public override void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument) { decoratedWriter.WritePreProcessorDirective(type, argument); } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertMissingTokensDecorator.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertMissingTokensDecorator.cs new file mode 100644 index 0000000000..8ea3797c43 --- /dev/null +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertMissingTokensDecorator.cs @@ -0,0 +1,112 @@ +// 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); + currentList.Add(t); + 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; + } + if (t != null) currentList.Add(t); + base.WriteKeyword(role, keyword); + } + + public override void WriteIdentifier(Identifier identifier) + { + 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/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertRequiredSpacesDecorator.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertRequiredSpacesDecorator.cs index a13b135843..36023451d3 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertRequiredSpacesDecorator.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertRequiredSpacesDecorator.cs @@ -49,7 +49,7 @@ namespace ICSharpCode.NRefactory.CSharp Division } - public InsertRequiredSpacesDecorator(ITokenWriter writer) + public InsertRequiredSpacesDecorator(TokenWriter writer) : base(writer) { } @@ -148,7 +148,7 @@ namespace ICSharpCode.NRefactory.CSharp lastWritten = LastWritten.Whitespace; } - public override void WritePrimitiveValue(object value) + public override void WritePrimitiveValue(object value, string literalValue = null) { base.WritePrimitiveValue(value); if (value == null || value is bool) @@ -177,9 +177,18 @@ namespace ICSharpCode.NRefactory.CSharp 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/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertSpecialsDecorator.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertSpecialsDecorator.cs index 3e38917dda..0fafdeef09 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertSpecialsDecorator.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/InsertSpecialsDecorator.cs @@ -29,7 +29,7 @@ namespace ICSharpCode.NRefactory.CSharp readonly Stack positionStack = new Stack(); int visitorWroteNewLine = 0; - public InsertSpecialsDecorator(ITokenWriter writer) : base(writer) + public InsertSpecialsDecorator(TokenWriter writer) : base(writer) { } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/TextWriterOutputFormatter.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/TextWriterOutputFormatter.cs index 8d0e664998..5f881f2c88 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/TextWriterOutputFormatter.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/OutputVisitor/TextWriterOutputFormatter.cs @@ -26,20 +26,21 @@ namespace ICSharpCode.NRefactory.CSharp /// /// Writes C# code into a TextWriter. /// - public class TextWriterTokenWriter : ITokenWriter + 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; } @@ -50,34 +51,42 @@ namespace ICSharpCode.NRefactory.CSharp throw new ArgumentNullException("textWriter"); this.textWriter = textWriter; this.IndentationString = "\t"; + this.line = 1; + this.column = 1; } - public void WriteIdentifier(Identifier identifier) + public override void WriteIdentifier(Identifier identifier) { WriteIndentation(); - if (identifier.IsVerbatim) + if (identifier.IsVerbatim) { textWriter.Write('@'); + column++; + } textWriter.Write(identifier.Name); + column += identifier.Name.Length; isAtStartOfLine = false; } - public void WriteKeyword(Role role, string keyword) + public override void WriteKeyword(Role role, string keyword) { WriteIndentation(); + column += keyword.Length; textWriter.Write(keyword); isAtStartOfLine = false; } - public void WriteToken(Role role, 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(' '); } @@ -88,33 +97,37 @@ namespace ICSharpCode.NRefactory.CSharp 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; @@ -122,31 +135,59 @@ namespace ICSharpCode.NRefactory.CSharp 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(); } @@ -159,42 +200,61 @@ namespace ICSharpCode.NRefactory.CSharp return writer.ToString(); } - public void WritePrimitiveValue(object value) + 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) { - textWriter.Write("\"" + ConvertString(value.ToString()) + "\""); + string tmp = "\"" + ConvertString(value.ToString()) + "\""; + column += tmp.Length; + textWriter.Write(tmp); } else if (value is char) { - textWriter.Write("'" + ConvertCharLiteral((char)value) + "'"); + string tmp = "'" + ConvertCharLiteral((char)value) + "'"; + column += tmp.Length; + textWriter.Write(tmp); } else if (value is decimal) { - textWriter.Write(((decimal)value).ToString(NumberFormatInfo.InvariantInfo) + "m"); + 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; } @@ -203,21 +263,28 @@ namespace ICSharpCode.NRefactory.CSharp // (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++; } - textWriter.Write(f.ToString("R", NumberFormatInfo.InvariantInfo) + "f"); + 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"); - textWriter.Write("."); + 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; } @@ -312,7 +379,17 @@ namespace ICSharpCode.NRefactory.CSharp return sb.ToString(); } - public virtual void StartNode(AstNode node) + 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 @@ -320,7 +397,7 @@ namespace ICSharpCode.NRefactory.CSharp WriteIndentation(); } - public virtual void EndNode(AstNode node) + public override void EndNode(AstNode node) { } } diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/InsertMissingTokensDecoratorTests.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/InsertMissingTokensDecoratorTests.cs new file mode 100644 index 0000000000..3ca4094201 --- /dev/null +++ b/src/Libraries/NRefactory/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] + 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/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Parser/ConsistencyChecker.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Parser/ConsistencyChecker.cs new file mode 100644 index 0000000000..a7344b9c76 --- /dev/null +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Parser/ConsistencyChecker.cs @@ -0,0 +1,90 @@ +// 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 (node is CSharpTokenNode) { + 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 void CheckWhitespace(AstNode startNode, TextLocation whitespaceStart, AstNode endNode, TextLocation whitespaceEnd, string currentFileName, IDocument currentDocument) + { + Assert.Greater(whitespaceStart.Line, 0); + Assert.Greater(whitespaceStart.Column, 0); + Assert.Greater(whitespaceEnd.Line, 0); + Assert.Greater(whitespaceEnd.Column, 0); + if (whitespaceStart == whitespaceEnd || startNode == endNode) + 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 () +")"); + } + } +} diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Parser/ParseSelfTests.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Parser/ParseSelfTests.cs index df744650a0..9b2f59d162 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Parser/ParseSelfTests.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Parser/ParseSelfTests.cs @@ -67,89 +67,20 @@ namespace ICSharpCode.NRefactory.CSharp.Parser } #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); - } - } - - 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 (node is CSharpTokenNode) { - 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); - } - } - - void CheckWhitespace(AstNode startNode, TextLocation whitespaceStart, AstNode endNode, TextLocation whitespaceEnd) - { - if (whitespaceStart == whitespaceEnd || startNode == endNode) - 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); + ConsistencyChecker.CheckPositionConsistency(syntaxTree, fileName, currentDocument); + ConsistencyChecker.CheckMissingTokens(syntaxTree, fileName, currentDocument); } - 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/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj index 8bf7a1b19a..c3b49170ca 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj @@ -159,8 +159,10 @@ + +