Browse Source

Squashed 'NRefactory/' changes from a20e80a..ec42611

ec42611 Escape contextual keywords within query expressions.
7ba4e0b Add parenthesis around query expressions within type tests "(from a in b select c) is D"
7312982 Bugfixes for InsertParenthesesVisitor and OutputVisitor.
afd9650 Add query expressions to AST and output visitor.
880d23b Bug fixes in OutputVisitor.
594a637 Enable automatic removal when replacing a node with its own descendant.

git-subtree-dir: NRefactory
git-subtree-split: ec42611f0e7be19c53a16b2902808bd4d894b1ea
pull/129/head
Daniel Grunwald 14 years ago
parent
commit
d87c5ea2c8
  1. 148
      ICSharpCode.NRefactory.Tests/CSharp/InsertParenthesesVisitorTests.cs
  2. 58
      ICSharpCode.NRefactory/CSharp/Ast/AstNode.cs
  3. 44
      ICSharpCode.NRefactory/CSharp/Ast/DepthFirstAstVisitor.cs
  4. 5
      ICSharpCode.NRefactory/CSharp/Ast/Expressions/Expression.cs
  5. 410
      ICSharpCode.NRefactory/CSharp/Ast/Expressions/QueryExpression.cs
  6. 11
      ICSharpCode.NRefactory/CSharp/Ast/IAstVisitor.cs
  7. 3
      ICSharpCode.NRefactory/CSharp/Ast/NodeType.cs
  8. 15
      ICSharpCode.NRefactory/CSharp/OutputVisitor/IOutputFormatter.cs
  9. 51
      ICSharpCode.NRefactory/CSharp/OutputVisitor/InsertParenthesesVisitor.cs
  10. 257
      ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs
  11. 17
      ICSharpCode.NRefactory/CSharp/OutputVisitor/TextWriterOutputFormatter.cs

148
ICSharpCode.NRefactory.Tests/CSharp/InsertParenthesesVisitorTests.cs

@ -10,12 +10,20 @@ namespace ICSharpCode.NRefactory.CSharp @@ -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 @@ -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 @@ -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));
}
}
}

58
ICSharpCode.NRefactory/CSharp/Ast/AstNode.cs

@ -129,6 +129,26 @@ namespace ICSharpCode.NRefactory.CSharp @@ -129,6 +129,26 @@ namespace ICSharpCode.NRefactory.CSharp
}
}
/// <summary>
/// Gets the ancestors of this node (excluding this node itself)
/// </summary>
public IEnumerable<AstNode> Ancestors {
get {
for (AstNode cur = parent; cur != null; cur = cur.parent) {
yield return cur;
}
}
}
/// <summary>
/// Gets all descendants of this node (excluding this node itself).
/// </summary>
public IEnumerable<AstNode> Descendants {
get {
return Utils.TreeTraversal.PreOrder(this.Children, n => n.Children);
}
}
/// <summary>
/// Gets the first child with the specified role.
/// Returns the role's null object if the child is not found.
@ -291,20 +311,27 @@ namespace ICSharpCode.NRefactory.CSharp @@ -291,20 +311,27 @@ namespace ICSharpCode.NRefactory.CSharp
Remove();
return;
}
if (this.parent == null) {
if (newNode == this)
return; // nothing to do...
if (parent == null) {
throw new InvalidOperationException(this.IsNull ? "Cannot replace the null nodes" : "Cannot replace the root node");
}
if (newNode.parent != null) {
// TODO: what if newNode is used within *this* tree?
// e.g. "parenthesizedExpr.ReplaceWith(parenthesizedExpr.Expression);"
// We'll probably want to allow that.
throw new ArgumentException ("Node is already used in another tree.", "newNode");
}
// Because this method doesn't statically check the new node's type with the role,
// we perform a runtime test:
if (!role.IsValid(newNode)) {
throw new ArgumentException (string.Format("The new node '{0}' is not valid in the role {1}", newNode.GetType().Name, role.ToString()), "newNode");
}
if (newNode.parent != null) {
// newNode is used within this tree?
if (newNode.Ancestors.Contains(this)) {
// e.g. "parenthesizedExpr.ReplaceWith(parenthesizedExpr.Expression);"
// enable automatic removal
newNode.Remove();
} else {
throw new ArgumentException ("Node is already used in another tree.", "newNode");
}
}
newNode.parent = parent;
newNode.role = role;
newNode.prevSibling = prevSibling;
@ -335,6 +362,9 @@ namespace ICSharpCode.NRefactory.CSharp @@ -335,6 +362,9 @@ namespace ICSharpCode.NRefactory.CSharp
{
if (replaceFunction == null)
throw new ArgumentNullException("replaceFunction");
if (parent == null) {
throw new InvalidOperationException(this.IsNull ? "Cannot replace the null nodes" : "Cannot replace the root node");
}
AstNode oldParent = parent;
AstNode oldSuccessor = nextSibling;
Role oldRole = role;
@ -402,13 +432,15 @@ namespace ICSharpCode.NRefactory.CSharp @@ -402,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;
}
}

44
ICSharpCode.NRefactory/CSharp/Ast/DepthFirstAstVisitor.cs

@ -455,47 +455,55 @@ namespace ICSharpCode.NRefactory.CSharp @@ -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)
{

5
ICSharpCode.NRefactory/CSharp/Ast/Expressions/Expression.cs

@ -117,6 +117,11 @@ namespace ICSharpCode.NRefactory.CSharp @@ -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
}
}

410
ICSharpCode.NRefactory/CSharp/Ast/Expressions/QueryExpression.cs

@ -1,322 +1,354 @@ @@ -1,322 +1,354 @@
//
// QueryExpression.cs
//
// Author:
// Mike Krüger <mkrueger@novell.com>
//
// 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<QueryClause> ClauseRole = new Role<QueryClause>("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<T, S> (AstVisitor<T, S> visitor, T data)
{
return default (S);
}
}
#endregion
public AstNode Expression {
get { return GetChildByRole (Roles.Expression) ?? AstNode.Null; }
public IEnumerable<QueryClause> Clauses {
get { return GetChildrenByRole(ClauseRole); }
set { SetChildrenByRole(ClauseRole, value); }
}
public override S AcceptVisitor<T, S> (AstVisitor<T, S> visitor, T data)
public override S AcceptVisitor<T, S>(AstVisitor<T, S> 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; }
}
}
/// <summary>
/// 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)
/// }
/// </summary>
public class QueryContinuationClause : QueryClause
{
public static readonly Role<QueryExpression> PrecedingQueryRole = new Role<QueryExpression>("PrecedingQuery", QueryExpression.Null);
public static readonly Role<CSharpTokenNode> 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<T, S> (AstVisitor<T, S> visitor, T data)
{
return visitor.VisitQueryContinuationClause (this, data);
}
}
public class QueryFromClause : QueryClause
{
public static readonly Role<CSharpTokenNode> FromKeywordRole = Roles.Keyword;
public static readonly Role<CSharpTokenNode> 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<T, S> (AstVisitor<T, S> 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<T, S> (AstVisitor<T, S> 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<T, S> (AstVisitor<T, S> visitor, T data)
{
return visitor.VisitQueryExpressionGroupClause (this, data);
return visitor.VisitQueryWhereClause (this, data);
}
}
public class QueryExpressionLetClause : AstNode
/// <summary>
/// Represents a join or group join clause.
/// </summary>
public class QueryJoinClause : QueryClause
{
public override NodeType NodeType {
get {
return NodeType.Unknown;
}
public static readonly Role<CSharpTokenNode> JoinKeywordRole = Roles.Keyword;
public static readonly Role<AstType> TypeRole = Roles.Type;
public static readonly Role<Identifier> JoinIdentifierRole = Roles.Identifier;
public static readonly Role<CSharpTokenNode> InKeywordRole = Roles.InKeyword;
public static readonly Role<Expression> InExpressionRole = Roles.Expression;
public static readonly Role<CSharpTokenNode> OnKeywordRole = new Role<CSharpTokenNode>("OnKeyword", CSharpTokenNode.Null);
public static readonly Role<Expression> OnExpressionRole = new Role<Expression>("OnExpression", Expression.Null);
public static readonly Role<CSharpTokenNode> EqualsKeywordRole = new Role<CSharpTokenNode>("EqualsKeyword", CSharpTokenNode.Null);
public static readonly Role<Expression> EqualsExpressionRole = new Role<Expression>("EqualsExpression", Expression.Null);
public static readonly Role<CSharpTokenNode> IntoKeywordRole = new Role<CSharpTokenNode>("IntoKeyword", CSharpTokenNode.Null);
public static readonly Role<Identifier> IntoIdentifierRole = new Role<Identifier>("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<T, S> (AstVisitor<T, S> 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<T, S> (AstVisitor<T, S> visitor, T data)
{
return visitor.VisitQueryJoinClause (this, data);
}
}
public class QueryOrderClause : QueryClause
{
public static readonly Role<QueryOrdering> OrderingRole = new Role<QueryOrdering>("Ordering");
public CSharpTokenNode Keyword {
get { return (CSharpTokenNode)GetChildByRole (Roles.Keyword); }
get { return GetChildByRole (Roles.Keyword); }
}
public IEnumerable<QueryOrdering> Orderings {
get { return GetChildrenByRole (OrderingRole); }
set { SetChildrenByRole (OrderingRole, value); }
}
public override S AcceptVisitor<T, S> (AstVisitor<T, S> 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<T, S> (AstVisitor<T, S> 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<T, S> (AstVisitor<T, S> 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<CSharpTokenNode> GroupKeywordRole = Roles.Keyword;
public static readonly Role<Expression> ProjectionRole = new Role<Expression>("Projection", Expression.Null);
public static readonly Role<CSharpTokenNode> ByKeywordRole = new Role<CSharpTokenNode>("ByKeyword", CSharpTokenNode.Null);
public static readonly Role<Expression> KeyRole = new Role<Expression>("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<T, S> (AstVisitor<T, S> visitor, T data)
{
return visitor.VisitQueryExpressionWhereClause (this, data);
return visitor.VisitQueryGroupClause (this, data);
}
}
*/
}

11
ICSharpCode.NRefactory/CSharp/Ast/IAstVisitor.cs

@ -42,6 +42,17 @@ namespace ICSharpCode.NRefactory.CSharp @@ -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);

3
ICSharpCode.NRefactory/CSharp/Ast/NodeType.cs

@ -42,7 +42,8 @@ namespace ICSharpCode.NRefactory.CSharp @@ -42,7 +42,8 @@ namespace ICSharpCode.NRefactory.CSharp
Member,
Statement,
Expression,
Token
Token,
QueryClause
}
}

15
ICSharpCode.NRefactory/CSharp/OutputVisitor/IOutputFormatter.cs

@ -10,8 +10,21 @@ namespace ICSharpCode.NRefactory.CSharp @@ -10,8 +10,21 @@ namespace ICSharpCode.NRefactory.CSharp
/// </summary>
public interface IOutputFormatter
{
void WriteIdentifier(string ident);
/// <summary>
/// Writes an identifier.
/// If the identifier conflicts with a keyword, the output visitor will
/// call <c>WriteToken("@")</c> before calling WriteIdentifier().
/// </summary>
void WriteIdentifier(string identifier);
/// <summary>
/// Writes a keyword to the output.
/// </summary>
void WriteKeyword(string keyword);
/// <summary>
/// Writes a token to the output.
/// </summary>
void WriteToken(string token);
void Space();

51
ICSharpCode.NRefactory/CSharp/OutputVisitor/InsertParenthesesVisitor.cs

@ -20,12 +20,13 @@ namespace ICSharpCode.NRefactory.CSharp @@ -20,12 +20,13 @@ namespace ICSharpCode.NRefactory.CSharp
/// </summary>
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;
/// <summary>
/// Gets the row number in the C# 4.0 spec operator precedence table.
@ -33,6 +34,11 @@ namespace ICSharpCode.NRefactory.CSharp @@ -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 @@ -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 @@ -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 @@ -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);
}
}
}

257
ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs

@ -22,7 +22,22 @@ namespace ICSharpCode.NRefactory.CSharp @@ -22,7 +22,22 @@ namespace ICSharpCode.NRefactory.CSharp
AstNode currentContainerNode;
readonly Stack<AstNode> positionStack = new Stack<AstNode>();
char lastChar;
/// <summary>
/// Used to insert the minimal amount of spaces so that the lexer recognizes the tokens that were written.
/// </summary>
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 @@ -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 @@ -178,26 +193,50 @@ namespace ICSharpCode.NRefactory.CSharp
void WriteKeyword(string keyword, Role<CSharpTokenNode> 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<Identifier> 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<CSharpTokenNode> 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 @@ -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<string> unconditionalKeywords = new HashSet<string> {
"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<string> queryKeywords = new HashSet<string> {
"from", "where", "join", "on", "equals", "into", "let", "orderby",
"ascending", "descending", "select", "group", "by"
};
/// <summary>
/// Determines whether the specified identifier is a keyword in the given context.
/// </summary>
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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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;
}

17
ICSharpCode.NRefactory/CSharp/OutputVisitor/TextWriterOutputFormatter.cs

@ -90,7 +90,22 @@ namespace ICSharpCode.NRefactory.CSharp @@ -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;
}
}
}
}

Loading…
Cancel
Save