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
[TestFixture] [TestFixture]
public class InsertParenthesesVisitorTests public class InsertParenthesesVisitorTests
{ {
CSharpFormattingPolicy policy;
[SetUp]
public void SetUp()
{
policy = new CSharpFormattingPolicy();
}
string InsertReadable(Expression expr) string InsertReadable(Expression expr)
{ {
expr = expr.Clone(); expr = expr.Clone();
expr.AcceptVisitor(new InsertParenthesesVisitor { InsertParenthesesForReadability = true }, null); expr.AcceptVisitor(new InsertParenthesesVisitor { InsertParenthesesForReadability = true }, null);
StringWriter w = new StringWriter(); StringWriter w = new StringWriter();
expr.AcceptVisitor(new OutputVisitor(w, new CSharpFormattingPolicy()), null); expr.AcceptVisitor(new OutputVisitor(w, policy), null);
return w.ToString(); return w.ToString();
} }
@ -24,7 +32,7 @@ namespace ICSharpCode.NRefactory.CSharp
expr = expr.Clone(); expr = expr.Clone();
expr.AcceptVisitor(new InsertParenthesesVisitor { InsertParenthesesForReadability = false }, null); expr.AcceptVisitor(new InsertParenthesesVisitor { InsertParenthesesForReadability = false }, null);
StringWriter w = new StringWriter(); StringWriter w = new StringWriter();
expr.AcceptVisitor(new OutputVisitor(w, new CSharpFormattingPolicy()), null); expr.AcceptVisitor(new OutputVisitor(w, policy), null);
return w.ToString(); return w.ToString();
} }
@ -87,5 +95,141 @@ namespace ICSharpCode.NRefactory.CSharp
Assert.AreEqual("((string)a).Length", InsertRequired(expr)); Assert.AreEqual("((string)a).Length", InsertRequired(expr));
Assert.AreEqual("((string)a).Length", InsertReadable(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
} }
} }
/// <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> /// <summary>
/// Gets the first child with the specified role. /// Gets the first child with the specified role.
/// Returns the role's null object if the child is not found. /// Returns the role's null object if the child is not found.
@ -291,20 +311,27 @@ namespace ICSharpCode.NRefactory.CSharp
Remove(); Remove();
return; 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"); 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, // Because this method doesn't statically check the new node's type with the role,
// we perform a runtime test: // we perform a runtime test:
if (!role.IsValid(newNode)) { 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"); 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.parent = parent;
newNode.role = role; newNode.role = role;
newNode.prevSibling = prevSibling; newNode.prevSibling = prevSibling;
@ -335,6 +362,9 @@ namespace ICSharpCode.NRefactory.CSharp
{ {
if (replaceFunction == null) if (replaceFunction == null)
throw new ArgumentNullException("replaceFunction"); 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 oldParent = parent;
AstNode oldSuccessor = nextSibling; AstNode oldSuccessor = nextSibling;
Role oldRole = role; Role oldRole = role;
@ -402,13 +432,15 @@ namespace ICSharpCode.NRefactory.CSharp
public object Clone() public object Clone()
{ {
AnnotationList copy = new AnnotationList(this.Count); lock (this) {
for (int i = 0; i < this.Count; i++) { AnnotationList copy = new AnnotationList(this.Count);
object obj = this[i]; for (int i = 0; i < this.Count; i++) {
ICloneable c = obj as ICloneable; object obj = this[i];
copy.Add(c != null ? c.Clone() : obj); 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
return VisitChildren (uncheckedExpression, data); return VisitChildren (uncheckedExpression, data);
} }
/* public virtual S VisitQueryExpression(QueryExpression queryExpression, T data)
public virtual S VisitQueryExpressionFromClause (QueryExpressionFromClause queryExpressionFromClause, 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) public virtual S VisitAsExpression (AsExpression asExpression, T data)
{ {

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

@ -117,6 +117,11 @@ namespace ICSharpCode.NRefactory.CSharp
{ {
return new AsExpression { Type = type, Expression = this }; return new AsExpression { Type = type, Expression = this };
} }
public IsExpression IsType(AstType type)
{
return new IsExpression { Type = type, Expression = this };
}
#endregion #endregion
} }
} }

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

@ -1,322 +1,354 @@
// // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// QueryExpression.cs // This code is distributed under MIT X11 license (for details please see \doc\license.txt)
//
// 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.
using System;
using System.Collections.Generic;
using System.Linq;
namespace ICSharpCode.NRefactory.CSharp namespace ICSharpCode.NRefactory.CSharp
{ {
/* TODO: how do we represent clauses? is QueryExpressionFromClause an expression, public class QueryExpression : Expression
* or do we introduce a parent QueryExpression?
public class QueryExpressionFromClause : AstNode
{ {
public const int FromKeywordRole = 100; public static readonly Role<QueryClause> ClauseRole = new Role<QueryClause>("Clause");
public const int InKeywordRole = 101;
public AstType Type { #region Null
get { return GetChildByRole (Roles.Type); } public new static readonly QueryExpression Null = new NullQueryExpression ();
set { SetChildByRole (Roles.Type, value); }
}
public string Identifier { sealed class NullQueryExpression : QueryExpression
get { {
return QueryIdentifier.Name; public override bool IsNull {
get {
return true;
}
} }
}
public override S AcceptVisitor<T, S> (AstVisitor<T, S> visitor, T data)
public Identifier QueryIdentifier { {
get { return default (S);
return (Identifier)GetChildByRole (Roles.Identifier) ?? ICSharpCode.NRefactory.CSharp.Identifier.Null;
} }
} }
#endregion
public AstNode Expression { public IEnumerable<QueryClause> Clauses {
get { return GetChildByRole (Roles.Expression) ?? AstNode.Null; } 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 override NodeType NodeType {
public const int EqualsExpressionRole = 101; get { return NodeType.QueryClause; }
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); }
} }
}
/// <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 { get {
return GetChildByRole (OnExpressionRole); return GetChildByRole (Roles.Identifier).Name;
}
set {
SetChildByRole(Roles.Identifier, new Identifier(value, AstLocation.Empty));
} }
} }
public AstNode EqualsExpression { public override S AcceptVisitor<T, S> (AstVisitor<T, S> visitor, T data)
get { {
return GetChildByRole (EqualsExpressionRole); 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 { public AstType Type {
get { get { return GetChildByRole (Roles.Type); }
return IntoIdentifierIdentifier.Name; set { SetChildByRole (Roles.Type, value); }
}
} }
public Identifier IntoIdentifierIdentifier { public string Identifier {
get { get {
return (Identifier)GetChildByRole (IntoIdentifierRole); return GetChildByRole (Roles.Identifier).Name;
}
set {
SetChildByRole(Roles.Identifier, new Identifier(value, AstLocation.Empty));
} }
} }
public AstNode InExpression { public Expression Expression {
get { get { return GetChildByRole (Roles.Expression); }
return GetChildByRole (Roles.Expression); set { SetChildByRole (Roles.Expression, 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.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 { get {
return NodeType.Unknown; return GetChildByRole(Roles.Identifier).Name;
}
set {
SetChildByRole(Roles.Identifier, new Identifier(value, AstLocation.Empty));
} }
} }
public const int ProjectionExpressionRole = 100; public CSharpTokenNode AssignToken {
public const int GroupByExpressionRole = 101; get { return GetChildByRole(Roles.Assign); }
public const int GroupKeywordRole = 102;
public const int ByKeywordRole = 103;
public CSharpTokenNode GroupKeyword {
get { return (CSharpTokenNode)GetChildByRole (GroupKeywordRole); }
} }
public CSharpTokenNode ByKeyword { public Expression Expression {
get { return (CSharpTokenNode)GetChildByRole (ByKeywordRole); } get { return GetChildByRole(Roles.Expression); }
set { SetChildByRole(Roles.Expression, value); }
} }
public AstNode Projection { public override S AcceptVisitor<T, S> (AstVisitor<T, S> visitor, T data)
get { {
return GetChildByRole (ProjectionExpressionRole); return visitor.VisitQueryLetClause (this, data);
} }
}
public class QueryWhereClause : QueryClause
{
public CSharpTokenNode WhereKeyword {
get { return GetChildByRole (Roles.Keyword); }
} }
public AstNode GroupBy { public Expression Condition {
get { get { return GetChildByRole (Roles.Condition); }
return GetChildByRole (GroupByExpressionRole); set { SetChildByRole (Roles.Condition, 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.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 { public static readonly Role<CSharpTokenNode> JoinKeywordRole = Roles.Keyword;
get { public static readonly Role<AstType> TypeRole = Roles.Type;
return NodeType.Unknown; 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 { public CSharpTokenNode JoinKeyword {
get { get { return GetChildByRole (JoinKeywordRole); }
return QueryIdentifier.Name;
}
} }
public Identifier QueryIdentifier { public AstType Type {
get { get { return GetChildByRole (TypeRole); }
return (Identifier)GetChildByRole (Roles.Identifier); set { SetChildByRole (TypeRole, value); }
}
} }
public AstNode Expression { public string JoinIdentifier {
get { get {
return GetChildByRole (Roles.Expression); return GetChildByRole(JoinIdentifierRole).Name;
}
set {
SetChildByRole(JoinIdentifierRole, new Identifier(value, AstLocation.Empty));
} }
} }
public CSharpTokenNode LetKeyword { public CSharpTokenNode InKeyword {
get { return (CSharpTokenNode)GetChildByRole (Roles.Keyword); } get { return GetChildByRole (InKeywordRole); }
} }
public AstNode Assign { public Expression InExpression {
get { get { return GetChildByRole (InExpressionRole); }
return GetChildByRole (Roles.Assign); set { SetChildByRole (InExpressionRole, value); }
}
} }
public override S AcceptVisitor<T, S> (AstVisitor<T, S> visitor, T data) public CSharpTokenNode OnKeyword {
{ get { return GetChildByRole (OnKeywordRole); }
return visitor.VisitQueryExpressionLetClause (this, data);
} }
}
public class QueryExpressionOrderClause : AstNode
{
public const int OrderingRole = 100;
public override NodeType NodeType { public Expression OnExpression {
get { get { return GetChildByRole (OnExpressionRole); }
return NodeType.Unknown; set { SetChildByRole (OnExpressionRole, value); }
}
} }
public bool OrderAscending { public CSharpTokenNode EqualsKeyword {
get; get { return GetChildByRole (EqualsKeywordRole); }
set; }
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 { 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 { 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) 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 { public override NodeType NodeType {
get { get { return NodeType.Unknown; }
return NodeType.Unknown; }
}
public Expression Expression {
get { return GetChildByRole (Roles.Expression); }
set { SetChildByRole (Roles.Expression, value); }
} }
public QueryExpressionOrderingDirection Direction { public QueryOrderingDirection Direction {
get; get;
set; set;
} }
public AstNode Criteria { public CSharpTokenNode DirectionToken {
get { get { return GetChildByRole (Roles.Keyword); }
return GetChildByRole (Roles.Expression);
}
} }
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.VisitQueryExpressionOrdering (this, data); return visitor.VisitQueryOrdering (this, data);
} }
} }
public enum QueryExpressionOrderingDirection public enum QueryOrderingDirection
{ {
Unknown, None,
Ascending, Ascending,
Descending Descending
} }
public class QueryExpressionSelectClause : AstNode public class QuerySelectClause : QueryClause
{ {
public override NodeType NodeType {
get {
return NodeType.Unknown;
}
}
public CSharpTokenNode SelectKeyword { public CSharpTokenNode SelectKeyword {
get { return (CSharpTokenNode)GetChildByRole (Roles.Keyword); } get { return GetChildByRole (Roles.Keyword); }
} }
public AstNode Projection { public Expression Expression {
get { get { return GetChildByRole (Roles.Expression); }
return GetChildByRole (Roles.Expression); set { SetChildByRole (Roles.Expression, 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.VisitQueryExpressionSelectClause (this, data); return visitor.VisitQuerySelectClause (this, data);
} }
} }
public class QueryExpressionWhereClause : AstNode public class QueryGroupClause : QueryClause
{ {
public override NodeType NodeType { public static readonly Role<CSharpTokenNode> GroupKeywordRole = Roles.Keyword;
get { public static readonly Role<Expression> ProjectionRole = new Role<Expression>("Projection", Expression.Null);
return NodeType.Unknown; 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 { public Expression Projection {
get { return (CSharpTokenNode)GetChildByRole (Roles.Keyword); } get { return GetChildByRole (ProjectionRole); }
set { SetChildByRole (ProjectionRole, value); }
} }
public AstNode Condition { public CSharpTokenNode ByKeyword {
get { get { return GetChildByRole (ByKeywordRole); }
return GetChildByRole (Roles.Condition); }
}
public Expression Key {
get { return GetChildByRole (KeyRole); }
set { SetChildByRole (KeyRole, 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.VisitQueryExpressionWhereClause (this, data); return visitor.VisitQueryGroupClause (this, data);
} }
} }
*/
} }

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

@ -42,6 +42,17 @@ namespace ICSharpCode.NRefactory.CSharp
S VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression, T data); S VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression, T data);
S VisitUncheckedExpression(UncheckedExpression uncheckedExpression, 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 VisitAttribute(Attribute attribute, T data);
S VisitAttributeSection(AttributeSection attributeSection, T data); S VisitAttributeSection(AttributeSection attributeSection, T data);
S VisitDelegateDeclaration(DelegateDeclaration delegateDeclaration, T data); S VisitDelegateDeclaration(DelegateDeclaration delegateDeclaration, T data);

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

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

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

@ -10,8 +10,21 @@ namespace ICSharpCode.NRefactory.CSharp
/// </summary> /// </summary>
public interface IOutputFormatter 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); void WriteKeyword(string keyword);
/// <summary>
/// Writes a token to the output.
/// </summary>
void WriteToken(string token); void WriteToken(string token);
void Space(); void Space();

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

@ -20,12 +20,13 @@ namespace ICSharpCode.NRefactory.CSharp
/// </summary> /// </summary>
public bool InsertParenthesesForReadability { get; set; } public bool InsertParenthesesForReadability { get; set; }
const int Primary = 15; const int Primary = 16;
const int QueryOrLambda = 15;
const int Unary = 14; const int Unary = 14;
const int RelationalAndTypeTesting = 10; const int RelationalAndTypeTesting = 10;
const int Equality = 9; const int Equality = 9;
const int Conditional = 2; const int Conditional = 2;
const int AssignmentAndLambda = 1; const int Assignment = 1;
/// <summary> /// <summary>
/// Gets the row number in the C# 4.0 spec operator precedence table. /// 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) static int GetPrecedence(Expression expr)
{ {
// Note: the operator precedence table on MSDN is incorrect // 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; UnaryOperatorExpression uoe = expr as UnaryOperatorExpression;
if (uoe != null) { if (uoe != null) {
if (uoe.Operator == UnaryOperatorType.PostDecrement || uoe.Operator == UnaryOperatorType.PostIncrement) if (uoe.Operator == UnaryOperatorType.PostDecrement || uoe.Operator == UnaryOperatorType.PostIncrement)
@ -84,7 +90,7 @@ namespace ICSharpCode.NRefactory.CSharp
if (expr is ConditionalExpression) if (expr is ConditionalExpression)
return Conditional; return Conditional;
if (expr is AssignmentExpression || expr is LambdaExpression) if (expr is AssignmentExpression || expr is LambdaExpression)
return AssignmentAndLambda; return Assignment;
// anything else: primary expression // anything else: primary expression
return Primary; return Primary;
} }
@ -132,24 +138,22 @@ namespace ICSharpCode.NRefactory.CSharp
// Unary expressions // Unary expressions
public override object VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression, object data) 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); return base.VisitUnaryOperatorExpression(unaryOperatorExpression, data);
} }
public override object VisitCastExpression(CastExpression castExpression, object 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 // 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. // "(int)-1" is fine, but "(A)-b" is not a cast.
UnaryOperatorExpression uoe = castExpression.Expression as UnaryOperatorExpression; UnaryOperatorExpression uoe = castExpression.Expression as UnaryOperatorExpression;
if (InsertParenthesesForReadability) { if (uoe != null && !(uoe.Operator == UnaryOperatorType.BitNot || uoe.Operator == UnaryOperatorType.Not)) {
if (uoe != null) if (TypeCanBeMisinterpretedAsExpression(castExpression.Type)) {
Parenthesize(castExpression.Expression); 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); return base.VisitCastExpression(castExpression, data);
@ -238,15 +242,34 @@ namespace ICSharpCode.NRefactory.CSharp
public override object VisitAssignmentExpression(AssignmentExpression assignmentExpression, object data) public override object VisitAssignmentExpression(AssignmentExpression assignmentExpression, object data)
{ {
// assignment is right-associative // assignment is right-associative
ParenthesizeIfRequired(assignmentExpression.Left, AssignmentAndLambda + 1); ParenthesizeIfRequired(assignmentExpression.Left, Assignment + 1);
if (InsertParenthesesForReadability) { if (InsertParenthesesForReadability) {
ParenthesizeIfRequired(assignmentExpression.Right, RelationalAndTypeTesting + 1); ParenthesizeIfRequired(assignmentExpression.Right, RelationalAndTypeTesting + 1);
} else { } else {
ParenthesizeIfRequired(assignmentExpression.Right, AssignmentAndLambda); ParenthesizeIfRequired(assignmentExpression.Right, Assignment);
} }
return base.VisitAssignmentExpression(assignmentExpression, data); return base.VisitAssignmentExpression(assignmentExpression, data);
} }
// don't need to handle lambdas, they have lowest precedence and unambiguous associativity // 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
AstNode currentContainerNode; AstNode currentContainerNode;
readonly Stack<AstNode> positionStack = new Stack<AstNode>(); 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) public OutputVisitor(TextWriter textWriter, CSharpFormattingPolicy formattingPolicy)
{ {
@ -131,7 +146,7 @@ namespace ICSharpCode.NRefactory.CSharp
WriteSpecialsUpToRole(AstNode.Roles.Comma, nextNode); WriteSpecialsUpToRole(AstNode.Roles.Comma, nextNode);
Space(policy.SpacesBeforeComma); Space(policy.SpacesBeforeComma);
formatter.WriteToken(","); formatter.WriteToken(",");
lastChar = ','; lastWritten = LastWritten.Other;
Space(policy.SpacesAfterComma); Space(policy.SpacesAfterComma);
} }
@ -178,26 +193,50 @@ namespace ICSharpCode.NRefactory.CSharp
void WriteKeyword(string keyword, Role<CSharpTokenNode> tokenRole = null) void WriteKeyword(string keyword, Role<CSharpTokenNode> tokenRole = null)
{ {
WriteSpecialsUpToRole(tokenRole ?? AstNode.Roles.Keyword); WriteSpecialsUpToRole(tokenRole ?? AstNode.Roles.Keyword);
if (lastChar == 'a') if (lastWritten == LastWritten.KeywordOrIdentifier)
Space(); formatter.Space();
formatter.WriteKeyword(keyword); formatter.WriteKeyword(keyword);
lastChar = 'a'; lastWritten = LastWritten.KeywordOrIdentifier;
} }
void WriteIdentifier(string identifier, Role<Identifier> identifierRole = null) void WriteIdentifier(string identifier, Role<Identifier> identifierRole = null)
{ {
WriteSpecialsUpToRole(identifierRole ?? AstNode.Roles.Identifier); WriteSpecialsUpToRole(identifierRole ?? AstNode.Roles.Identifier);
if (lastChar == 'a') if (IsKeyword(identifier, currentContainerNode)) {
Space(); formatter.WriteToken("@");
} else if (lastWritten == LastWritten.KeywordOrIdentifier) {
formatter.Space();
}
formatter.WriteIdentifier(identifier); formatter.WriteIdentifier(identifier);
lastChar = 'a'; lastWritten = LastWritten.KeywordOrIdentifier;
} }
void WriteToken(string token, Role<CSharpTokenNode> tokenRole) void WriteToken(string token, Role<CSharpTokenNode> tokenRole)
{ {
WriteSpecialsUpToRole(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); 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() void LPar()
@ -228,28 +267,62 @@ namespace ICSharpCode.NRefactory.CSharp
{ {
if (addSpace) { if (addSpace) {
formatter.Space(); formatter.Space();
lastChar = ' '; lastWritten = LastWritten.Whitespace;
} }
} }
void NewLine() void NewLine()
{ {
formatter.NewLine(); formatter.NewLine();
lastChar = '\n'; lastWritten = LastWritten.Whitespace;
} }
void OpenBrace(BraceStyle style) void OpenBrace(BraceStyle style)
{ {
WriteSpecialsUpToRole(AstNode.Roles.LBrace); WriteSpecialsUpToRole(AstNode.Roles.LBrace);
formatter.OpenBrace(style); formatter.OpenBrace(style);
lastChar = '{'; lastWritten = LastWritten.Other;
} }
void CloseBrace(BraceStyle style) void CloseBrace(BraceStyle style)
{ {
WriteSpecialsUpToRole(AstNode.Roles.RBrace); WriteSpecialsUpToRole(AstNode.Roles.RBrace);
formatter.CloseBrace(style); 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 #endregion
@ -285,13 +358,15 @@ namespace ICSharpCode.NRefactory.CSharp
foreach (Identifier ident in identifiers) { foreach (Identifier ident in identifiers) {
if (first) { if (first) {
first = false; first = false;
if (lastWritten == LastWritten.KeywordOrIdentifier)
formatter.Space();
} else { } else {
WriteSpecialsUpToRole(AstNode.Roles.Dot, ident); WriteSpecialsUpToRole(AstNode.Roles.Dot, ident);
} }
WriteSpecialsUpToNode(ident); WriteSpecialsUpToNode(ident);
formatter.WriteIdentifier(ident.Name); formatter.WriteIdentifier(ident.Name);
lastChar = 'a'; lastWritten = LastWritten.KeywordOrIdentifier;
} }
} }
@ -660,7 +735,7 @@ namespace ICSharpCode.NRefactory.CSharp
{ {
StartNode(primitiveExpression); StartNode(primitiveExpression);
formatter.WriteToken(ToCSharpString(primitiveExpression)); formatter.WriteToken(ToCSharpString(primitiveExpression));
lastChar = 'a'; lastWritten = LastWritten.Other;
return EndNode(primitiveExpression); return EndNode(primitiveExpression);
} }
@ -821,8 +896,13 @@ namespace ICSharpCode.NRefactory.CSharp
public object VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression, object data) public object VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression, object data)
{ {
StartNode(unaryOperatorExpression); 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); unaryOperatorExpression.Expression.AcceptVisitor(this, data);
if (opType == UnaryOperatorType.PostIncrement || opType == UnaryOperatorType.PostDecrement)
WriteToken(opSymbol, UnaryOperatorExpression.OperatorRole);
return EndNode(unaryOperatorExpression); return EndNode(unaryOperatorExpression);
} }
@ -839,6 +919,143 @@ namespace ICSharpCode.NRefactory.CSharp
} }
#endregion #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 #region GeneralScope
public object VisitAttribute(Attribute attribute, object data) public object VisitAttribute(Attribute attribute, object data)
{ {
@ -1633,7 +1850,7 @@ namespace ICSharpCode.NRefactory.CSharp
foreach (var comma in arraySpecifier.GetChildrenByRole(ArraySpecifier.Roles.Comma)) { foreach (var comma in arraySpecifier.GetChildrenByRole(ArraySpecifier.Roles.Comma)) {
WriteSpecialsUpToNode(comma); WriteSpecialsUpToNode(comma);
formatter.WriteToken(","); formatter.WriteToken(",");
lastChar = ','; lastWritten = LastWritten.Other;
} }
WriteToken("]", ArraySpecifier.Roles.RBracket); WriteToken("]", ArraySpecifier.Roles.RBracket);
return EndNode(arraySpecifier); return EndNode(arraySpecifier);
@ -1648,7 +1865,13 @@ namespace ICSharpCode.NRefactory.CSharp
public object VisitComment(Comment comment, object data) 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); formatter.WriteComment(comment.CommentType, comment.Content);
lastWritten = LastWritten.Whitespace;
return null; return null;
} }

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

@ -90,7 +90,22 @@ namespace ICSharpCode.NRefactory.CSharp
public void WriteComment(CommentType commentType, string content) 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