diff --git a/ICSharpCode.NRefactory.Tests/CSharp/InsertParenthesesVisitorTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/InsertParenthesesVisitorTests.cs index 6cafb6a5a2..1415ca3f38 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/InsertParenthesesVisitorTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/InsertParenthesesVisitorTests.cs @@ -10,12 +10,20 @@ namespace ICSharpCode.NRefactory.CSharp [TestFixture] public class InsertParenthesesVisitorTests { + CSharpFormattingPolicy policy; + + [SetUp] + public void SetUp() + { + policy = new CSharpFormattingPolicy(); + } + string InsertReadable(Expression expr) { expr = expr.Clone(); expr.AcceptVisitor(new InsertParenthesesVisitor { InsertParenthesesForReadability = true }, null); StringWriter w = new StringWriter(); - expr.AcceptVisitor(new OutputVisitor(w, new CSharpFormattingPolicy()), null); + expr.AcceptVisitor(new OutputVisitor(w, policy), null); return w.ToString(); } @@ -24,7 +32,7 @@ namespace ICSharpCode.NRefactory.CSharp expr = expr.Clone(); expr.AcceptVisitor(new InsertParenthesesVisitor { InsertParenthesesForReadability = false }, null); StringWriter w = new StringWriter(); - expr.AcceptVisitor(new OutputVisitor(w, new CSharpFormattingPolicy()), null); + expr.AcceptVisitor(new OutputVisitor(w, policy), null); return w.ToString(); } @@ -87,5 +95,141 @@ namespace ICSharpCode.NRefactory.CSharp Assert.AreEqual("((string)a).Length", InsertRequired(expr)); Assert.AreEqual("((string)a).Length", InsertReadable(expr)); } + + [Test] + public void DoubleNegation() + { + Expression expr = new UnaryOperatorExpression( + UnaryOperatorType.Minus, + new UnaryOperatorExpression(UnaryOperatorType.Minus, new IdentifierExpression("a")) + ); + + Assert.AreEqual("- -a", InsertRequired(expr)); + Assert.AreEqual("-(-a)", InsertReadable(expr)); + } + + [Test] + public void TypeTestInConditional() + { + Expression expr = new ConditionalExpression { + Condition = new IdentifierExpression("a").IsType( + new ComposedType { + BaseType = new PrimitiveType("int"), + HasNullableSpecifier = true + } + ), + TrueExpression = new IdentifierExpression("b"), + FalseExpression = new IdentifierExpression("c") + }; + + Assert.AreEqual("a is int? ? b : c", InsertRequired(expr)); + Assert.AreEqual("(a is int?) ? b : c", InsertReadable(expr)); + + policy.ConditionalOperatorBeforeConditionSpace = false; + policy.ConditionalOperatorAfterConditionSpace = false; + policy.ConditionalOperatorBeforeSeparatorSpace = false; + policy.ConditionalOperatorAfterSeparatorSpace = false; + + Assert.AreEqual("a is int? ?b:c", InsertRequired(expr)); + Assert.AreEqual("(a is int?)?b:c", InsertReadable(expr)); + } + + [Test] + public void MethodCallOnQueryExpression() + { + Expression expr = new QueryExpression { + Clauses = new QueryClause[] { + new QueryFromClause { + Identifier = "a", + Expression = new IdentifierExpression("b") + }, + new QuerySelectClause { + Expression = new IdentifierExpression("a").Invoke("c") + } + } + }.Invoke("ToArray"); + + Assert.AreEqual("(from a in b" + Environment.NewLine + "select a.c ()).ToArray ()", InsertRequired(expr)); + Assert.AreEqual("(from a in b" + Environment.NewLine + "select a.c ()).ToArray ()", InsertReadable(expr)); + } + + [Test] + public void SumOfQueries() + { + QueryExpression query = new QueryExpression { + Clauses = new QueryClause[] { + new QueryFromClause { + Identifier = "a", + Expression = new IdentifierExpression("b") + }, + new QuerySelectClause { + Expression = new IdentifierExpression("a") + } + } + }; + Expression expr = new BinaryOperatorExpression( + query, + BinaryOperatorType.Add, + query.Clone() + ); + + Assert.AreEqual("(from a in b" + Environment.NewLine + + "select a) + from a in b" + Environment.NewLine + + "select a", InsertRequired(expr)); + Assert.AreEqual("(from a in b" + Environment.NewLine + + "select a) + (from a in b" + Environment.NewLine + + "select a)", InsertReadable(expr)); + } + + [Test] + public void QueryInTypeTest() + { + Expression expr = new QueryExpression { + Clauses = new QueryClause[] { + new QueryFromClause { + Identifier = "a", + Expression = new IdentifierExpression("b") + }, + new QuerySelectClause { + Expression = new IdentifierExpression("a") + } + } + }.IsType(new PrimitiveType("int")); + + Assert.AreEqual("(from a in b" + Environment.NewLine + + "select a) is int", InsertRequired(expr)); + Assert.AreEqual("(from a in b" + Environment.NewLine + + "select a) is int", InsertReadable(expr)); + } + + [Test] + public void PrePost() + { + Expression expr = new UnaryOperatorExpression( + UnaryOperatorType.Increment, + new UnaryOperatorExpression( + UnaryOperatorType.PostIncrement, + new IdentifierExpression("a") + ) + ); + + Assert.AreEqual("++a++", InsertRequired(expr)); + Assert.AreEqual("++(a++)", InsertReadable(expr)); + } + + [Test] + public void PostPre() + { + Expression expr = new UnaryOperatorExpression( + UnaryOperatorType.PostIncrement, + new UnaryOperatorExpression( + UnaryOperatorType.Increment, + new IdentifierExpression("a") + ) + ); + + Assert.AreEqual("(++a)++", InsertRequired(expr)); + Assert.AreEqual("(++a)++", InsertReadable(expr)); + } } } diff --git a/ICSharpCode.NRefactory/CSharp/Ast/AstNode.cs b/ICSharpCode.NRefactory/CSharp/Ast/AstNode.cs index 2b6f1915b5..f05be52cfd 100644 --- a/ICSharpCode.NRefactory/CSharp/Ast/AstNode.cs +++ b/ICSharpCode.NRefactory/CSharp/Ast/AstNode.cs @@ -432,13 +432,15 @@ namespace ICSharpCode.NRefactory.CSharp public object Clone() { - AnnotationList copy = new AnnotationList(this.Count); - for (int i = 0; i < this.Count; i++) { - object obj = this[i]; - ICloneable c = obj as ICloneable; - copy.Add(c != null ? c.Clone() : obj); + lock (this) { + AnnotationList copy = new AnnotationList(this.Count); + for (int i = 0; i < this.Count; i++) { + object obj = this[i]; + ICloneable c = obj as ICloneable; + copy.Add(c != null ? c.Clone() : obj); + } + return copy; } - return copy; } } diff --git a/ICSharpCode.NRefactory/CSharp/Ast/DepthFirstAstVisitor.cs b/ICSharpCode.NRefactory/CSharp/Ast/DepthFirstAstVisitor.cs index d3ce63aff1..f862c80ef6 100644 --- a/ICSharpCode.NRefactory/CSharp/Ast/DepthFirstAstVisitor.cs +++ b/ICSharpCode.NRefactory/CSharp/Ast/DepthFirstAstVisitor.cs @@ -455,47 +455,55 @@ namespace ICSharpCode.NRefactory.CSharp return VisitChildren (uncheckedExpression, data); } - /* - public virtual S VisitQueryExpressionFromClause (QueryExpressionFromClause queryExpressionFromClause, T data) + public virtual S VisitQueryExpression(QueryExpression queryExpression, T data) { - return VisitChildren (queryExpressionFromClause, data); + return VisitChildren (queryExpression, data); } - public virtual S VisitQueryExpressionWhereClause (QueryExpressionWhereClause queryExpressionWhereClause, T data) + public virtual S VisitQueryContinuationClause(QueryContinuationClause queryContinuationClause, T data) { - return VisitChildren (queryExpressionWhereClause, data); + return VisitChildren (queryContinuationClause, data); } - public virtual S VisitQueryExpressionJoinClause (QueryExpressionJoinClause queryExpressionJoinClause, T data) + public virtual S VisitQueryFromClause(QueryFromClause queryFromClause, T data) { - return VisitChildren (queryExpressionJoinClause, data); + return VisitChildren (queryFromClause, data); } - public virtual S VisitQueryExpressionGroupClause (QueryExpressionGroupClause queryExpressionGroupClause, T data) + public virtual S VisitQueryLetClause(QueryLetClause queryLetClause, T data) { - return VisitChildren (queryExpressionGroupClause, data); + return VisitChildren (queryLetClause, data); } - public virtual S VisitQueryExpressionLetClause (QueryExpressionLetClause queryExpressionLetClause, T data) + public virtual S VisitQueryWhereClause(QueryWhereClause queryWhereClause, T data) { - return VisitChildren (queryExpressionLetClause, data); + return VisitChildren (queryWhereClause, data); } - public virtual S VisitQueryExpressionOrderClause (QueryExpressionOrderClause queryExpressionOrderClause, T data) + public virtual S VisitQueryJoinClause(QueryJoinClause queryJoinClause, T data) { - return VisitChildren (queryExpressionOrderClause, data); + return VisitChildren (queryJoinClause, data); } - public virtual S VisitQueryExpressionOrdering (QueryExpressionOrdering queryExpressionOrdering, T data) + public virtual S VisitQueryOrderClause(QueryOrderClause queryOrderClause, T data) { - return VisitChildren (queryExpressionOrdering, data); + return VisitChildren (queryOrderClause, data); } - public virtual S VisitQueryExpressionSelectClause (QueryExpressionSelectClause queryExpressionSelectClause, T data) + public virtual S VisitQueryOrdering(QueryOrdering queryOrdering, T data) { - return VisitChildren (queryExpressionSelectClause, data); + return VisitChildren (queryOrdering, data); + } + + public virtual S VisitQuerySelectClause(QuerySelectClause querySelectClause, T data) + { + return VisitChildren (querySelectClause, data); + } + + public virtual S VisitQueryGroupClause(QueryGroupClause queryGroupClause, T data) + { + return VisitChildren (queryGroupClause, data); } - */ public virtual S VisitAsExpression (AsExpression asExpression, T data) { diff --git a/ICSharpCode.NRefactory/CSharp/Ast/Expressions/Expression.cs b/ICSharpCode.NRefactory/CSharp/Ast/Expressions/Expression.cs index 3aa04402b5..7a7e9790b0 100644 --- a/ICSharpCode.NRefactory/CSharp/Ast/Expressions/Expression.cs +++ b/ICSharpCode.NRefactory/CSharp/Ast/Expressions/Expression.cs @@ -117,6 +117,11 @@ namespace ICSharpCode.NRefactory.CSharp { return new AsExpression { Type = type, Expression = this }; } + + public IsExpression IsType(AstType type) + { + return new IsExpression { Type = type, Expression = this }; + } #endregion } } diff --git a/ICSharpCode.NRefactory/CSharp/Ast/Expressions/QueryExpression.cs b/ICSharpCode.NRefactory/CSharp/Ast/Expressions/QueryExpression.cs index e369ad7f91..eb4e77fa48 100644 --- a/ICSharpCode.NRefactory/CSharp/Ast/Expressions/QueryExpression.cs +++ b/ICSharpCode.NRefactory/CSharp/Ast/Expressions/QueryExpression.cs @@ -1,322 +1,354 @@ -// -// QueryExpression.cs -// -// Author: -// Mike Krüger -// -// Copyright (c) 2009 Novell, Inc (http://www.novell.com) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under MIT X11 license (for details please see \doc\license.txt) +using System; +using System.Collections.Generic; +using System.Linq; namespace ICSharpCode.NRefactory.CSharp { - /* TODO: how do we represent clauses? is QueryExpressionFromClause an expression, - * or do we introduce a parent QueryExpression? - public class QueryExpressionFromClause : AstNode + public class QueryExpression : Expression { - public const int FromKeywordRole = 100; - public const int InKeywordRole = 101; + public static readonly Role ClauseRole = new Role("Clause"); - public AstType Type { - get { return GetChildByRole (Roles.Type); } - set { SetChildByRole (Roles.Type, value); } - } + #region Null + public new static readonly QueryExpression Null = new NullQueryExpression (); - public string Identifier { - get { - return QueryIdentifier.Name; + sealed class NullQueryExpression : QueryExpression + { + public override bool IsNull { + get { + return true; + } } - } - - public Identifier QueryIdentifier { - get { - return (Identifier)GetChildByRole (Roles.Identifier) ?? ICSharpCode.NRefactory.CSharp.Identifier.Null; + + public override S AcceptVisitor (AstVisitor visitor, T data) + { + return default (S); } } + #endregion - public AstNode Expression { - get { return GetChildByRole (Roles.Expression) ?? AstNode.Null; } + public IEnumerable Clauses { + get { return GetChildrenByRole(ClauseRole); } + set { SetChildrenByRole(ClauseRole, value); } } - public override S AcceptVisitor (AstVisitor visitor, T data) + public override S AcceptVisitor(AstVisitor visitor, T data) { - return visitor.VisitQueryExpressionFromClause (this, data); + return visitor.VisitQueryExpression (this, data); } } - public class QueryExpressionJoinClause : QueryExpressionFromClause + public abstract class QueryClause : AstNode { - public const int OnExpressionRole = 100; - public const int EqualsExpressionRole = 101; - public const int IntoIdentifierRole = 102; - - public const int JoinKeywordRole = 110; - public new const int InKeywordRole = 111; - public const int OnKeywordRole = 112; - public const int EqualsKeywordRole = 113; - public const int IntoKeywordRole = 114; - - public CSharpTokenNode JoinKeyword { - get { return (CSharpTokenNode)GetChildByRole (JoinKeywordRole); } - } - public CSharpTokenNode InKeyword { - get { return (CSharpTokenNode)GetChildByRole (InKeywordRole); } - } - public CSharpTokenNode OnKeyword { - get { return (CSharpTokenNode)GetChildByRole (OnKeywordRole); } - } - public CSharpTokenNode EqualsKeyword { - get { return (CSharpTokenNode)GetChildByRole (EqualsKeywordRole); } - } - public CSharpTokenNode IntoKeyword { - get { return (CSharpTokenNode)GetChildByRole (IntoKeywordRole); } + public override NodeType NodeType { + get { return NodeType.QueryClause; } } + } + + /// + /// Represents a query continuation. + /// "(from .. select ..) into Identifier" or "(from .. group .. by ..) into Identifier" + /// Note that "join .. into .." is not a query continuation! + /// + /// This is always the first(!!) clause in a query expression. + /// The tree for "from a in b select c into d select e" looks like this: + /// new QueryExpression { + /// new QueryContinuationClause { + /// PrecedingQuery = new QueryExpression { + /// new QueryFromClause(a in b), + /// new QuerySelectClause(c) + /// }, + /// Identifier = d + /// }, + /// new QuerySelectClause(e) + /// } + /// + public class QueryContinuationClause : QueryClause + { + public static readonly Role PrecedingQueryRole = new Role("PrecedingQuery", QueryExpression.Null); + public static readonly Role IntoKeywordRole = Roles.Keyword; + public QueryExpression PrecedingQuery { + get { return GetChildByRole(PrecedingQueryRole); } + set { SetChildByRole(PrecedingQueryRole, value); } + } - public AstNode OnExpression { + public string Identifier { get { - return GetChildByRole (OnExpressionRole); + return GetChildByRole (Roles.Identifier).Name; + } + set { + SetChildByRole(Roles.Identifier, new Identifier(value, AstLocation.Empty)); } } - public AstNode EqualsExpression { - get { - return GetChildByRole (EqualsExpressionRole); - } + public override S AcceptVisitor (AstVisitor visitor, T data) + { + return visitor.VisitQueryContinuationClause (this, data); } + } + + public class QueryFromClause : QueryClause + { + public static readonly Role FromKeywordRole = Roles.Keyword; + public static readonly Role InKeywordRole = Roles.InKeyword; - public string IntoIdentifier { - get { - return IntoIdentifierIdentifier.Name; - } + public AstType Type { + get { return GetChildByRole (Roles.Type); } + set { SetChildByRole (Roles.Type, value); } } - public Identifier IntoIdentifierIdentifier { + public string Identifier { get { - return (Identifier)GetChildByRole (IntoIdentifierRole); + return GetChildByRole (Roles.Identifier).Name; + } + set { + SetChildByRole(Roles.Identifier, new Identifier(value, AstLocation.Empty)); } } - public AstNode InExpression { - get { - return GetChildByRole (Roles.Expression); - } + public Expression Expression { + get { return GetChildByRole (Roles.Expression); } + set { SetChildByRole (Roles.Expression, value); } } public override S AcceptVisitor (AstVisitor visitor, T data) { - return visitor.VisitQueryExpressionJoinClause (this, data); + return visitor.VisitQueryFromClause (this, data); } } - public class QueryExpressionGroupClause : AstNode + public class QueryLetClause : QueryClause { - public override NodeType NodeType { + public CSharpTokenNode LetKeyword { + get { return GetChildByRole(Roles.Keyword); } + } + + public string Identifier { get { - return NodeType.Unknown; + return GetChildByRole(Roles.Identifier).Name; + } + set { + SetChildByRole(Roles.Identifier, new Identifier(value, AstLocation.Empty)); } } - public const int ProjectionExpressionRole = 100; - public const int GroupByExpressionRole = 101; - - public const int GroupKeywordRole = 102; - public const int ByKeywordRole = 103; - - public CSharpTokenNode GroupKeyword { - get { return (CSharpTokenNode)GetChildByRole (GroupKeywordRole); } + public CSharpTokenNode AssignToken { + get { return GetChildByRole(Roles.Assign); } } - public CSharpTokenNode ByKeyword { - get { return (CSharpTokenNode)GetChildByRole (ByKeywordRole); } + public Expression Expression { + get { return GetChildByRole(Roles.Expression); } + set { SetChildByRole(Roles.Expression, value); } } - public AstNode Projection { - get { - return GetChildByRole (ProjectionExpressionRole); - } + public override S AcceptVisitor (AstVisitor visitor, T data) + { + return visitor.VisitQueryLetClause (this, data); + } + } + + + public class QueryWhereClause : QueryClause + { + public CSharpTokenNode WhereKeyword { + get { return GetChildByRole (Roles.Keyword); } } - public AstNode GroupBy { - get { - return GetChildByRole (GroupByExpressionRole); - } + public Expression Condition { + get { return GetChildByRole (Roles.Condition); } + set { SetChildByRole (Roles.Condition, value); } } public override S AcceptVisitor (AstVisitor visitor, T data) { - return visitor.VisitQueryExpressionGroupClause (this, data); + return visitor.VisitQueryWhereClause (this, data); } } - public class QueryExpressionLetClause : AstNode + /// + /// Represents a join or group join clause. + /// + public class QueryJoinClause : QueryClause { - public override NodeType NodeType { - get { - return NodeType.Unknown; - } + public static readonly Role JoinKeywordRole = Roles.Keyword; + public static readonly Role TypeRole = Roles.Type; + public static readonly Role JoinIdentifierRole = Roles.Identifier; + public static readonly Role InKeywordRole = Roles.InKeyword; + public static readonly Role InExpressionRole = Roles.Expression; + public static readonly Role OnKeywordRole = new Role("OnKeyword", CSharpTokenNode.Null); + public static readonly Role OnExpressionRole = new Role("OnExpression", Expression.Null); + public static readonly Role EqualsKeywordRole = new Role("EqualsKeyword", CSharpTokenNode.Null); + public static readonly Role EqualsExpressionRole = new Role("EqualsExpression", Expression.Null); + public static readonly Role IntoKeywordRole = new Role("IntoKeyword", CSharpTokenNode.Null); + public static readonly Role IntoIdentifierRole = new Role("IntoIdentifier", Identifier.Null); + + public bool IsGroupJoin { + get { return !string.IsNullOrEmpty(this.IntoIdentifier); } } - public string Identifier { - get { - return QueryIdentifier.Name; - } + public CSharpTokenNode JoinKeyword { + get { return GetChildByRole (JoinKeywordRole); } } - public Identifier QueryIdentifier { - get { - return (Identifier)GetChildByRole (Roles.Identifier); - } + public AstType Type { + get { return GetChildByRole (TypeRole); } + set { SetChildByRole (TypeRole, value); } } - public AstNode Expression { + public string JoinIdentifier { get { - return GetChildByRole (Roles.Expression); + return GetChildByRole(JoinIdentifierRole).Name; + } + set { + SetChildByRole(JoinIdentifierRole, new Identifier(value, AstLocation.Empty)); } } - public CSharpTokenNode LetKeyword { - get { return (CSharpTokenNode)GetChildByRole (Roles.Keyword); } + public CSharpTokenNode InKeyword { + get { return GetChildByRole (InKeywordRole); } } - public AstNode Assign { - get { - return GetChildByRole (Roles.Assign); - } + public Expression InExpression { + get { return GetChildByRole (InExpressionRole); } + set { SetChildByRole (InExpressionRole, value); } } - public override S AcceptVisitor (AstVisitor visitor, T data) - { - return visitor.VisitQueryExpressionLetClause (this, data); + public CSharpTokenNode OnKeyword { + get { return GetChildByRole (OnKeywordRole); } } - } - - public class QueryExpressionOrderClause : AstNode - { - public const int OrderingRole = 100; - public override NodeType NodeType { - get { - return NodeType.Unknown; - } + public Expression OnExpression { + get { return GetChildByRole (OnExpressionRole); } + set { SetChildByRole (OnExpressionRole, value); } } - public bool OrderAscending { - get; - set; + public CSharpTokenNode EqualsKeyword { + get { return GetChildByRole (EqualsKeywordRole); } + } + + public Expression EqualsExpression { + get { return GetChildByRole (EqualsExpressionRole); } + set { SetChildByRole (EqualsExpressionRole, value); } + } + + public CSharpTokenNode IntoKeyword { + get { return GetChildByRole (IntoKeywordRole); } } - public AstNode Expression { + public string IntoIdentifier { get { - return GetChildByRole (Roles.Expression); + return GetChildByRole (IntoIdentifierRole).Name; + } + set { + SetChildByRole(IntoIdentifierRole, new Identifier(value, AstLocation.Empty)); } } + public override S AcceptVisitor (AstVisitor visitor, T data) + { + return visitor.VisitQueryJoinClause (this, data); + } + } + + public class QueryOrderClause : QueryClause + { + public static readonly Role OrderingRole = new Role("Ordering"); + public CSharpTokenNode Keyword { - get { return (CSharpTokenNode)GetChildByRole (Roles.Keyword); } + get { return GetChildByRole (Roles.Keyword); } + } + + public IEnumerable Orderings { + get { return GetChildrenByRole (OrderingRole); } + set { SetChildrenByRole (OrderingRole, value); } } public override S AcceptVisitor (AstVisitor visitor, T data) { - return visitor.VisitQueryExpressionOrderClause (this, data); + return visitor.VisitQueryOrderClause (this, data); } } - public class QueryExpressionOrdering : AstNode + public class QueryOrdering : AstNode { public override NodeType NodeType { - get { - return NodeType.Unknown; - } + get { return NodeType.Unknown; } + } + + public Expression Expression { + get { return GetChildByRole (Roles.Expression); } + set { SetChildByRole (Roles.Expression, value); } } - public QueryExpressionOrderingDirection Direction { + public QueryOrderingDirection Direction { get; set; } - public AstNode Criteria { - get { - return GetChildByRole (Roles.Expression); - } + public CSharpTokenNode DirectionToken { + get { return GetChildByRole (Roles.Keyword); } } public override S AcceptVisitor (AstVisitor visitor, T data) { - return visitor.VisitQueryExpressionOrdering (this, data); + return visitor.VisitQueryOrdering (this, data); } } - public enum QueryExpressionOrderingDirection + public enum QueryOrderingDirection { - Unknown, + None, Ascending, Descending } - public class QueryExpressionSelectClause : AstNode + public class QuerySelectClause : QueryClause { - public override NodeType NodeType { - get { - return NodeType.Unknown; - } - } - public CSharpTokenNode SelectKeyword { - get { return (CSharpTokenNode)GetChildByRole (Roles.Keyword); } + get { return GetChildByRole (Roles.Keyword); } } - public AstNode Projection { - get { - return GetChildByRole (Roles.Expression); - } + public Expression Expression { + get { return GetChildByRole (Roles.Expression); } + set { SetChildByRole (Roles.Expression, value); } } public override S AcceptVisitor (AstVisitor visitor, T data) { - return visitor.VisitQueryExpressionSelectClause (this, data); + return visitor.VisitQuerySelectClause (this, data); } } - public class QueryExpressionWhereClause : AstNode + public class QueryGroupClause : QueryClause { - public override NodeType NodeType { - get { - return NodeType.Unknown; - } + public static readonly Role GroupKeywordRole = Roles.Keyword; + public static readonly Role ProjectionRole = new Role("Projection", Expression.Null); + public static readonly Role ByKeywordRole = new Role("ByKeyword", CSharpTokenNode.Null); + public static readonly Role KeyRole = new Role("Key", Expression.Null); + + public CSharpTokenNode GroupKeyword { + get { return GetChildByRole (GroupKeywordRole); } } - public CSharpTokenNode WhereKeyword { - get { return (CSharpTokenNode)GetChildByRole (Roles.Keyword); } + public Expression Projection { + get { return GetChildByRole (ProjectionRole); } + set { SetChildByRole (ProjectionRole, value); } } - public AstNode Condition { - get { - return GetChildByRole (Roles.Condition); - } + public CSharpTokenNode ByKeyword { + get { return GetChildByRole (ByKeywordRole); } + } + + public Expression Key { + get { return GetChildByRole (KeyRole); } + set { SetChildByRole (KeyRole, value); } } public override S AcceptVisitor (AstVisitor visitor, T data) { - return visitor.VisitQueryExpressionWhereClause (this, data); + return visitor.VisitQueryGroupClause (this, data); } - } - */ } \ No newline at end of file diff --git a/ICSharpCode.NRefactory/CSharp/Ast/IAstVisitor.cs b/ICSharpCode.NRefactory/CSharp/Ast/IAstVisitor.cs index af5ec4477d..e61922a881 100644 --- a/ICSharpCode.NRefactory/CSharp/Ast/IAstVisitor.cs +++ b/ICSharpCode.NRefactory/CSharp/Ast/IAstVisitor.cs @@ -42,6 +42,17 @@ namespace ICSharpCode.NRefactory.CSharp S VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression, T data); S VisitUncheckedExpression(UncheckedExpression uncheckedExpression, T data); + S VisitQueryExpression(QueryExpression queryExpression, T data); + S VisitQueryContinuationClause(QueryContinuationClause queryContinuationClause, T data); + S VisitQueryFromClause(QueryFromClause queryFromClause, T data); + S VisitQueryLetClause(QueryLetClause queryLetClause, T data); + S VisitQueryWhereClause(QueryWhereClause queryWhereClause, T data); + S VisitQueryJoinClause(QueryJoinClause queryJoinClause, T data); + S VisitQueryOrderClause(QueryOrderClause queryOrderClause, T data); + S VisitQueryOrdering(QueryOrdering queryOrdering, T data); + S VisitQuerySelectClause(QuerySelectClause querySelectClause, T data); + S VisitQueryGroupClause(QueryGroupClause queryGroupClause, T data); + S VisitAttribute(Attribute attribute, T data); S VisitAttributeSection(AttributeSection attributeSection, T data); S VisitDelegateDeclaration(DelegateDeclaration delegateDeclaration, T data); diff --git a/ICSharpCode.NRefactory/CSharp/Ast/NodeType.cs b/ICSharpCode.NRefactory/CSharp/Ast/NodeType.cs index f17e41f424..d5d56fb4ca 100644 --- a/ICSharpCode.NRefactory/CSharp/Ast/NodeType.cs +++ b/ICSharpCode.NRefactory/CSharp/Ast/NodeType.cs @@ -42,7 +42,8 @@ namespace ICSharpCode.NRefactory.CSharp Member, Statement, Expression, - Token + Token, + QueryClause } } diff --git a/ICSharpCode.NRefactory/CSharp/OutputVisitor/IOutputFormatter.cs b/ICSharpCode.NRefactory/CSharp/OutputVisitor/IOutputFormatter.cs index 84816243aa..3aff02b5ff 100644 --- a/ICSharpCode.NRefactory/CSharp/OutputVisitor/IOutputFormatter.cs +++ b/ICSharpCode.NRefactory/CSharp/OutputVisitor/IOutputFormatter.cs @@ -10,8 +10,21 @@ namespace ICSharpCode.NRefactory.CSharp /// public interface IOutputFormatter { - void WriteIdentifier(string ident); + /// + /// 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(); diff --git a/ICSharpCode.NRefactory/CSharp/OutputVisitor/InsertParenthesesVisitor.cs b/ICSharpCode.NRefactory/CSharp/OutputVisitor/InsertParenthesesVisitor.cs index 0b93f72cc2..abdd06e630 100644 --- a/ICSharpCode.NRefactory/CSharp/OutputVisitor/InsertParenthesesVisitor.cs +++ b/ICSharpCode.NRefactory/CSharp/OutputVisitor/InsertParenthesesVisitor.cs @@ -20,12 +20,13 @@ namespace ICSharpCode.NRefactory.CSharp /// public bool InsertParenthesesForReadability { get; set; } - const int Primary = 15; + const int Primary = 16; + const int QueryOrLambda = 15; const int Unary = 14; const int RelationalAndTypeTesting = 10; const int Equality = 9; const int Conditional = 2; - const int AssignmentAndLambda = 1; + const int Assignment = 1; /// /// Gets the row number in the C# 4.0 spec operator precedence table. @@ -33,6 +34,11 @@ namespace ICSharpCode.NRefactory.CSharp static int GetPrecedence(Expression expr) { // Note: the operator precedence table on MSDN is incorrect + if (expr is QueryExpression) { + // Not part of the table in the C# spec, but we need to ensure that queries within + // primary expressions get parenthesized. + return QueryOrLambda; + } UnaryOperatorExpression uoe = expr as UnaryOperatorExpression; if (uoe != null) { if (uoe.Operator == UnaryOperatorType.PostDecrement || uoe.Operator == UnaryOperatorType.PostIncrement) @@ -84,7 +90,7 @@ namespace ICSharpCode.NRefactory.CSharp if (expr is ConditionalExpression) return Conditional; if (expr is AssignmentExpression || expr is LambdaExpression) - return AssignmentAndLambda; + return Assignment; // anything else: primary expression return Primary; } @@ -132,24 +138,22 @@ namespace ICSharpCode.NRefactory.CSharp // Unary expressions public override object VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression, object data) { - ParenthesizeIfRequired(unaryOperatorExpression.Expression, Unary); + ParenthesizeIfRequired(unaryOperatorExpression.Expression, GetPrecedence(unaryOperatorExpression)); + UnaryOperatorExpression child = unaryOperatorExpression.Expression as UnaryOperatorExpression; + if (child != null && InsertParenthesesForReadability) + Parenthesize(child); return base.VisitUnaryOperatorExpression(unaryOperatorExpression, data); } public override object VisitCastExpression(CastExpression castExpression, object data) { - ParenthesizeIfRequired(castExpression.Expression, Unary); + ParenthesizeIfRequired(castExpression.Expression, InsertParenthesesForReadability ? Primary : Unary); // There's a nasty issue in the C# grammar: cast expressions including certain operators are ambiguous in some cases // "(int)-1" is fine, but "(A)-b" is not a cast. UnaryOperatorExpression uoe = castExpression.Expression as UnaryOperatorExpression; - if (InsertParenthesesForReadability) { - if (uoe != null) + if (uoe != null && !(uoe.Operator == UnaryOperatorType.BitNot || uoe.Operator == UnaryOperatorType.Not)) { + if (TypeCanBeMisinterpretedAsExpression(castExpression.Type)) { Parenthesize(castExpression.Expression); - } else { - if (uoe != null && !(uoe.Operator == UnaryOperatorType.BitNot || uoe.Operator == UnaryOperatorType.Not)) { - if (TypeCanBeMisinterpretedAsExpression(castExpression.Type)) { - Parenthesize(castExpression.Expression); - } } } return base.VisitCastExpression(castExpression, data); @@ -238,15 +242,34 @@ namespace ICSharpCode.NRefactory.CSharp public override object VisitAssignmentExpression(AssignmentExpression assignmentExpression, object data) { // assignment is right-associative - ParenthesizeIfRequired(assignmentExpression.Left, AssignmentAndLambda + 1); + ParenthesizeIfRequired(assignmentExpression.Left, Assignment + 1); if (InsertParenthesesForReadability) { ParenthesizeIfRequired(assignmentExpression.Right, RelationalAndTypeTesting + 1); } else { - ParenthesizeIfRequired(assignmentExpression.Right, AssignmentAndLambda); + ParenthesizeIfRequired(assignmentExpression.Right, Assignment); } return base.VisitAssignmentExpression(assignmentExpression, data); } // don't need to handle lambdas, they have lowest precedence and unambiguous associativity + + public override object VisitQueryExpression(QueryExpression queryExpression, object data) + { + // Query expressions are strange beasts: + // "var a = -from b in c select d;" is valid, so queries bind stricter than unary expressions. + // However, the end of the query is greedy. So their start sort of has a high precedence, + // while their end has a very low precedence. We handle this by checking whether a query is used + // as left part of a binary operator, and parenthesize it if required. + if (queryExpression.Role == BinaryOperatorExpression.LeftRole) + Parenthesize(queryExpression); + if (queryExpression.Parent is IsExpression || queryExpression.Parent is AsExpression) + Parenthesize(queryExpression); + if (InsertParenthesesForReadability) { + // when readability is desired, always parenthesize query expressions within unary or binary operators + if (queryExpression.Parent is UnaryOperatorExpression || queryExpression.Parent is BinaryOperatorExpression) + Parenthesize(queryExpression); + } + return base.VisitQueryExpression(queryExpression, data); + } } } diff --git a/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs b/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs index 73fd1b9deb..c6e6605b23 100644 --- a/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs +++ b/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs @@ -22,7 +22,22 @@ namespace ICSharpCode.NRefactory.CSharp AstNode currentContainerNode; readonly Stack positionStack = new Stack(); - char lastChar; + + /// + /// 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, + QuestionMark, + Division + } public OutputVisitor(TextWriter textWriter, CSharpFormattingPolicy formattingPolicy) { @@ -131,7 +146,7 @@ namespace ICSharpCode.NRefactory.CSharp WriteSpecialsUpToRole(AstNode.Roles.Comma, nextNode); Space(policy.SpacesBeforeComma); formatter.WriteToken(","); - lastChar = ','; + lastWritten = LastWritten.Other; Space(policy.SpacesAfterComma); } @@ -178,26 +193,50 @@ namespace ICSharpCode.NRefactory.CSharp void WriteKeyword(string keyword, Role tokenRole = null) { WriteSpecialsUpToRole(tokenRole ?? AstNode.Roles.Keyword); - if (lastChar == 'a') - Space(); + if (lastWritten == LastWritten.KeywordOrIdentifier) + formatter.Space(); formatter.WriteKeyword(keyword); - lastChar = 'a'; + lastWritten = LastWritten.KeywordOrIdentifier; } void WriteIdentifier(string identifier, Role identifierRole = null) { WriteSpecialsUpToRole(identifierRole ?? AstNode.Roles.Identifier); - if (lastChar == 'a') - Space(); + if (IsKeyword(identifier, currentContainerNode)) { + formatter.WriteToken("@"); + } else if (lastWritten == LastWritten.KeywordOrIdentifier) { + formatter.Space(); + } formatter.WriteIdentifier(identifier); - lastChar = 'a'; + lastWritten = LastWritten.KeywordOrIdentifier; } 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; + // and for ?, this can happen in "a is int? ? b : c" or "a as int? ?? 0") + if (lastWritten == LastWritten.Plus && token[0] == '+' + || lastWritten == LastWritten.Minus && token[0] == '-' + || lastWritten == LastWritten.QuestionMark && token[0] == '?' + || lastWritten == LastWritten.Division && token[0] == '*') + { + formatter.Space(); + } formatter.WriteToken(token); - lastChar = token[token.Length - 1]; + if (token == "+") + lastWritten = LastWritten.Plus; + else if (token == "-") + lastWritten = LastWritten.Minus; + else if (token == "?") + lastWritten = LastWritten.QuestionMark; + else if (token == "/") + lastWritten = LastWritten.Division; + else + lastWritten = LastWritten.Other; } void LPar() @@ -228,28 +267,62 @@ namespace ICSharpCode.NRefactory.CSharp { if (addSpace) { formatter.Space(); - lastChar = ' '; + lastWritten = LastWritten.Whitespace; } } void NewLine() { formatter.NewLine(); - lastChar = '\n'; + lastWritten = LastWritten.Whitespace; } void OpenBrace(BraceStyle style) { WriteSpecialsUpToRole(AstNode.Roles.LBrace); formatter.OpenBrace(style); - lastChar = '{'; + lastWritten = LastWritten.Other; } void CloseBrace(BraceStyle style) { WriteSpecialsUpToRole(AstNode.Roles.RBrace); formatter.CloseBrace(style); - lastChar = '}'; + lastWritten = LastWritten.Other; + } + #endregion + + #region IsKeyword Test + static readonly HashSet unconditionalKeywords = new HashSet { + "abstract", "as", "base", "bool", "break", "byte", "case", "catch", + "char", "checked", "class", "const", "continue", "decimal", "default", "delegate", + "do", "double", "else", "enum", "event", "explicit", "extern", "false", + "finally", "fixed", "float", "for", "foreach", "goto", "if", "implicit", + "in", "int", "interface", "internal", "is", "lock", "long", "namespace", + "new", "null", "object", "operator", "out", "override", "params", "private", + "protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short", + "sizeof", "stackalloc", "static", "string", "struct", "switch", "this", "throw", + "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", + "using", "virtual", "void", "volatile", "while" + }; + + static readonly HashSet queryKeywords = new HashSet { + "from", "where", "join", "on", "equals", "into", "let", "orderby", + "ascending", "descending", "select", "group", "by" + }; + + /// + /// Determines whether the specified identifier is a keyword in the given context. + /// + public static bool IsKeyword(string identifier, AstNode context) + { + if (unconditionalKeywords.Contains(identifier)) + return true; + if (context.Ancestors.Any(a => a is QueryExpression)) { + if (queryKeywords.Contains(identifier)) + return true; + } + return false; } #endregion @@ -285,13 +358,15 @@ namespace ICSharpCode.NRefactory.CSharp foreach (Identifier ident in identifiers) { if (first) { first = false; + if (lastWritten == LastWritten.KeywordOrIdentifier) + formatter.Space(); } else { WriteSpecialsUpToRole(AstNode.Roles.Dot, ident); } WriteSpecialsUpToNode(ident); formatter.WriteIdentifier(ident.Name); - lastChar = 'a'; + lastWritten = LastWritten.KeywordOrIdentifier; } } @@ -660,7 +735,7 @@ namespace ICSharpCode.NRefactory.CSharp { StartNode(primitiveExpression); formatter.WriteToken(ToCSharpString(primitiveExpression)); - lastChar = 'a'; + lastWritten = LastWritten.Other; return EndNode(primitiveExpression); } @@ -821,8 +896,13 @@ namespace ICSharpCode.NRefactory.CSharp public object VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression, object data) { StartNode(unaryOperatorExpression); - WriteToken(UnaryOperatorExpression.GetOperatorSymbol(unaryOperatorExpression.Operator), UnaryOperatorExpression.OperatorRole); + UnaryOperatorType opType = unaryOperatorExpression.Operator; + string opSymbol = UnaryOperatorExpression.GetOperatorSymbol(opType); + if (!(opType == UnaryOperatorType.PostIncrement || opType == UnaryOperatorType.PostDecrement)) + WriteToken(opSymbol, UnaryOperatorExpression.OperatorRole); unaryOperatorExpression.Expression.AcceptVisitor(this, data); + if (opType == UnaryOperatorType.PostIncrement || opType == UnaryOperatorType.PostDecrement) + WriteToken(opSymbol, UnaryOperatorExpression.OperatorRole); return EndNode(unaryOperatorExpression); } @@ -839,6 +919,143 @@ namespace ICSharpCode.NRefactory.CSharp } #endregion + #region Query Expressions + public object VisitQueryExpression(QueryExpression queryExpression, object data) + { + StartNode(queryExpression); + bool first = true; + foreach (var clause in queryExpression.Clauses) { + if (first) { + first = false; + } else { + NewLine(); + } + clause.AcceptVisitor(this, data); + } + return EndNode(queryExpression); + } + + public object VisitQueryContinuationClause(QueryContinuationClause queryContinuationClause, object data) + { + StartNode(queryContinuationClause); + queryContinuationClause.PrecedingQuery.AcceptVisitor(this, data); + Space(); + WriteKeyword("into", QueryContinuationClause.IntoKeywordRole); + Space(); + WriteIdentifier(queryContinuationClause.Identifier); + return EndNode(queryContinuationClause); + } + + public object VisitQueryFromClause(QueryFromClause queryFromClause, object data) + { + StartNode(queryFromClause); + WriteKeyword("from", QueryFromClause.FromKeywordRole); + queryFromClause.Type.AcceptVisitor(this, data); + Space(); + WriteIdentifier(queryFromClause.Identifier); + WriteKeyword("in", QueryFromClause.InKeywordRole); + queryFromClause.Expression.AcceptVisitor(this, data); + return EndNode(queryFromClause); + } + + public object VisitQueryLetClause(QueryLetClause queryLetClause, object data) + { + StartNode(queryLetClause); + WriteKeyword("let"); + Space(); + WriteIdentifier(queryLetClause.Identifier); + Space(policy.AroundAssignmentParentheses); + WriteToken("=", QueryLetClause.Roles.Assign); + Space(policy.AroundAssignmentParentheses); + queryLetClause.Expression.AcceptVisitor(this, data); + return EndNode(queryLetClause); + } + + public object VisitQueryWhereClause(QueryWhereClause queryWhereClause, object data) + { + StartNode(queryWhereClause); + WriteKeyword("where"); + Space(); + queryWhereClause.Condition.AcceptVisitor(this, data); + return EndNode(queryWhereClause); + } + + public object VisitQueryJoinClause(QueryJoinClause queryJoinClause, object data) + { + StartNode(queryJoinClause); + WriteKeyword("join", QueryJoinClause.JoinKeywordRole); + queryJoinClause.Type.AcceptVisitor(this, data); + Space(); + WriteIdentifier(queryJoinClause.JoinIdentifier, QueryJoinClause.JoinIdentifierRole); + Space(); + WriteKeyword("in", QueryJoinClause.InKeywordRole); + Space(); + queryJoinClause.InExpression.AcceptVisitor(this, data); + Space(); + WriteKeyword("on", QueryJoinClause.OnKeywordRole); + Space(); + queryJoinClause.OnExpression.AcceptVisitor(this, data); + Space(); + WriteKeyword("equals", QueryJoinClause.EqualsKeywordRole); + Space(); + queryJoinClause.EqualsExpression.AcceptVisitor(this, data); + if (queryJoinClause.IsGroupJoin) { + Space(); + WriteKeyword("into", QueryJoinClause.IntoKeywordRole); + WriteIdentifier(queryJoinClause.IntoIdentifier, QueryJoinClause.IntoIdentifierRole); + } + return EndNode(queryJoinClause); + } + + public object VisitQueryOrderClause(QueryOrderClause queryOrderClause, object data) + { + StartNode(queryOrderClause); + WriteKeyword("orderby"); + Space(); + WriteCommaSeparatedList(queryOrderClause.Orderings); + return EndNode(queryOrderClause); + } + + public object VisitQueryOrdering(QueryOrdering queryOrdering, object data) + { + StartNode(queryOrdering); + queryOrdering.Expression.AcceptVisitor(this, data); + switch (queryOrdering.Direction) { + case QueryOrderingDirection.Ascending: + Space(); + WriteKeyword("ascending"); + break; + case QueryOrderingDirection.Descending: + Space(); + WriteKeyword("descending"); + break; + } + return EndNode(queryOrdering); + } + + public object VisitQuerySelectClause(QuerySelectClause querySelectClause, object data) + { + StartNode(querySelectClause); + WriteKeyword("select"); + Space(); + querySelectClause.Expression.AcceptVisitor(this, data); + return EndNode(querySelectClause); + } + + public object VisitQueryGroupClause(QueryGroupClause queryGroupClause, object data) + { + StartNode(queryGroupClause); + WriteKeyword("group", QueryGroupClause.GroupKeywordRole); + Space(); + queryGroupClause.Projection.AcceptVisitor(this, data); + Space(); + WriteKeyword("by", QueryGroupClause.ByKeywordRole); + Space(); + queryGroupClause.Key.AcceptVisitor(this, data); + return EndNode(queryGroupClause); + } + #endregion + #region GeneralScope public object VisitAttribute(Attribute attribute, object data) { @@ -1633,7 +1850,7 @@ namespace ICSharpCode.NRefactory.CSharp foreach (var comma in arraySpecifier.GetChildrenByRole(ArraySpecifier.Roles.Comma)) { WriteSpecialsUpToNode(comma); formatter.WriteToken(","); - lastChar = ','; + lastWritten = LastWritten.Other; } WriteToken("]", ArraySpecifier.Roles.RBracket); return EndNode(arraySpecifier); @@ -1648,7 +1865,13 @@ namespace ICSharpCode.NRefactory.CSharp public object VisitComment(Comment comment, object data) { + 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.WriteComment(comment.CommentType, comment.Content); + lastWritten = LastWritten.Whitespace; return null; } diff --git a/ICSharpCode.NRefactory/CSharp/OutputVisitor/TextWriterOutputFormatter.cs b/ICSharpCode.NRefactory/CSharp/OutputVisitor/TextWriterOutputFormatter.cs index b3a50cac01..e14d1032c3 100644 --- a/ICSharpCode.NRefactory/CSharp/OutputVisitor/TextWriterOutputFormatter.cs +++ b/ICSharpCode.NRefactory/CSharp/OutputVisitor/TextWriterOutputFormatter.cs @@ -90,7 +90,22 @@ namespace ICSharpCode.NRefactory.CSharp public void WriteComment(CommentType commentType, string content) { - throw new NotImplementedException(); + WriteIndentation(); + switch (commentType) { + case CommentType.SingleLine: + textWriter.Write("//"); + textWriter.WriteLine(content); + break; + case CommentType.MultiLine: + textWriter.Write("/*"); + textWriter.Write(content); + textWriter.Write("*/"); + break; + case CommentType.Documentation: + textWriter.Write("///"); + textWriter.WriteLine(content); + break; + } } } }